Move out app init (#4185)
Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
+42
-497
@@ -3,14 +3,11 @@ import { IpcRendererEvent } from 'electron';
|
||||
import { HashRouter, Routes, Route, useNavigate, useLocation } from 'react-router-dom';
|
||||
import { openSharedSessionFromDeepLink, type SessionLinksViewOptions } from './sessionLinks';
|
||||
import { type SharedSessionDetails } from './sharedSessions';
|
||||
import { initializeSystem } from './utils/providerUtils';
|
||||
import { initializeCostDatabase } from './utils/costDatabase';
|
||||
import { ErrorUI } from './components/ErrorBoundary';
|
||||
import { ConfirmationModal } from './components/ui/ConfirmationModal';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
import { extractExtensionName } from './components/settings/extensions/utils';
|
||||
import { GoosehintsModal } from './components/GoosehintsModal';
|
||||
import { type ExtensionConfig } from './extensions';
|
||||
import AnnouncementModal from './components/AnnouncementModal';
|
||||
import { generateSessionId } from './sessions';
|
||||
import ProviderGuard from './components/ProviderGuard';
|
||||
@@ -29,18 +26,10 @@ import { ChatProvider } from './contexts/ChatContext';
|
||||
import { DraftProvider } from './contexts/DraftContext';
|
||||
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import { useConfig, MalformedConfigError } from './components/ConfigContext';
|
||||
import { useConfig } from './components/ConfigContext';
|
||||
import { ModelAndProviderProvider } from './components/ModelAndProviderContext';
|
||||
import { addExtensionFromDeepLink as addExtensionFromDeepLinkV2 } from './components/settings/extensions';
|
||||
import {
|
||||
backupConfig,
|
||||
initConfig,
|
||||
readAllConfig,
|
||||
recoverConfig,
|
||||
validateConfig,
|
||||
} from './api/sdk.gen';
|
||||
import PermissionSettingsView from './components/settings/permission/PermissionSetting';
|
||||
import { COST_TRACKING_ENABLED } from './updates';
|
||||
|
||||
import { type SessionDetails } from './sessions';
|
||||
import ExtensionsView, { ExtensionsViewOptions } from './components/extensions/ExtensionsView';
|
||||
@@ -48,52 +37,9 @@ import { Recipe } from './recipe';
|
||||
import RecipesView from './components/RecipesView';
|
||||
import RecipeEditor from './components/RecipeEditor';
|
||||
|
||||
export type View =
|
||||
| 'welcome'
|
||||
| 'chat'
|
||||
| 'pair'
|
||||
| 'settings'
|
||||
| 'extensions'
|
||||
| 'moreModels'
|
||||
| 'configureProviders'
|
||||
| 'configPage'
|
||||
| 'ConfigureProviders'
|
||||
| 'settingsV2'
|
||||
| 'sessions'
|
||||
| 'schedules'
|
||||
| 'sharedSession'
|
||||
| 'loading'
|
||||
| 'recipeEditor'
|
||||
| 'recipes'
|
||||
| 'permission';
|
||||
|
||||
export type ViewOptions = {
|
||||
// Settings view options
|
||||
extensionId?: string;
|
||||
showEnvVars?: boolean;
|
||||
deepLinkConfig?: ExtensionConfig;
|
||||
|
||||
// Session view options
|
||||
resumedSession?: SessionDetails;
|
||||
sessionDetails?: SessionDetails;
|
||||
error?: string;
|
||||
shareToken?: string;
|
||||
baseUrl?: string;
|
||||
|
||||
// Recipe editor options
|
||||
config?: unknown;
|
||||
|
||||
// Permission view options
|
||||
parentView?: View;
|
||||
|
||||
// Generic options
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export type ViewConfig = {
|
||||
view: View;
|
||||
viewOptions?: ViewOptions;
|
||||
};
|
||||
// Import the new modules
|
||||
import { createNavigationHandler, View, ViewOptions } from './utils/navigationUtils';
|
||||
import { initializeApp } from './utils/appInitialization';
|
||||
|
||||
// Route Components
|
||||
const HubRouteWrapper = ({
|
||||
@@ -108,6 +54,7 @@ const HubRouteWrapper = ({
|
||||
setIsGoosehintsModalOpen: (isOpen: boolean) => void;
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const setView = createNavigationHandler(navigate);
|
||||
|
||||
return (
|
||||
<Hub
|
||||
@@ -115,46 +62,7 @@ const HubRouteWrapper = ({
|
||||
chat={chat}
|
||||
setChat={setChat}
|
||||
setPairChat={setPairChat}
|
||||
setView={(view: View, options?: ViewOptions) => {
|
||||
// Convert view to route navigation
|
||||
switch (view) {
|
||||
case 'chat':
|
||||
navigate('/');
|
||||
break;
|
||||
case 'pair':
|
||||
navigate('/pair', { state: options });
|
||||
break;
|
||||
case 'settings':
|
||||
navigate('/settings', { state: options });
|
||||
break;
|
||||
case 'sessions':
|
||||
navigate('/sessions');
|
||||
break;
|
||||
case 'schedules':
|
||||
navigate('/schedules');
|
||||
break;
|
||||
case 'recipes':
|
||||
navigate('/recipes');
|
||||
break;
|
||||
case 'permission':
|
||||
navigate('/permission', { state: options });
|
||||
break;
|
||||
case 'ConfigureProviders':
|
||||
navigate('/configure-providers');
|
||||
break;
|
||||
case 'sharedSession':
|
||||
navigate('/shared-session', { state: options });
|
||||
break;
|
||||
case 'recipeEditor':
|
||||
navigate('/recipe-editor', { state: options });
|
||||
break;
|
||||
case 'welcome':
|
||||
navigate('/welcome');
|
||||
break;
|
||||
default:
|
||||
navigate('/');
|
||||
}
|
||||
}}
|
||||
setView={setView}
|
||||
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
|
||||
/>
|
||||
);
|
||||
@@ -174,6 +82,7 @@ const PairRouteWrapper = ({
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const chatRef = useRef(chat);
|
||||
const setView = createNavigationHandler(navigate);
|
||||
|
||||
// Keep the ref updated with the current chat state
|
||||
useEffect(() => {
|
||||
@@ -273,46 +182,7 @@ const PairRouteWrapper = ({
|
||||
<Pair
|
||||
chat={chat}
|
||||
setChat={setChat}
|
||||
setView={(view: View, options?: ViewOptions) => {
|
||||
// Convert view to route navigation
|
||||
switch (view) {
|
||||
case 'chat':
|
||||
navigate('/');
|
||||
break;
|
||||
case 'pair':
|
||||
navigate('/pair', { state: options });
|
||||
break;
|
||||
case 'settings':
|
||||
navigate('/settings', { state: options });
|
||||
break;
|
||||
case 'sessions':
|
||||
navigate('/sessions');
|
||||
break;
|
||||
case 'schedules':
|
||||
navigate('/schedules');
|
||||
break;
|
||||
case 'recipes':
|
||||
navigate('/recipes');
|
||||
break;
|
||||
case 'permission':
|
||||
navigate('/permission', { state: options });
|
||||
break;
|
||||
case 'ConfigureProviders':
|
||||
navigate('/configure-providers');
|
||||
break;
|
||||
case 'sharedSession':
|
||||
navigate('/shared-session', { state: options });
|
||||
break;
|
||||
case 'recipeEditor':
|
||||
navigate('/recipe-editor', { state: options });
|
||||
break;
|
||||
case 'welcome':
|
||||
navigate('/welcome');
|
||||
break;
|
||||
default:
|
||||
navigate('/');
|
||||
}
|
||||
}}
|
||||
setView={setView}
|
||||
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
|
||||
/>
|
||||
);
|
||||
@@ -321,105 +191,19 @@ const PairRouteWrapper = ({
|
||||
const SettingsRoute = () => {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const setView = createNavigationHandler(navigate);
|
||||
|
||||
// Get viewOptions from location.state or history.state
|
||||
const viewOptions =
|
||||
(location.state as SettingsViewOptions) || (window.history.state as SettingsViewOptions) || {};
|
||||
return (
|
||||
<SettingsView
|
||||
onClose={() => navigate('/')}
|
||||
setView={(view: View, options?: ViewOptions) => {
|
||||
// Convert view to route navigation
|
||||
switch (view) {
|
||||
case 'chat':
|
||||
navigate('/');
|
||||
break;
|
||||
case 'pair':
|
||||
navigate('/pair');
|
||||
break;
|
||||
case 'settings':
|
||||
navigate('/settings', { state: options });
|
||||
break;
|
||||
case 'sessions':
|
||||
navigate('/sessions');
|
||||
break;
|
||||
case 'schedules':
|
||||
navigate('/schedules');
|
||||
break;
|
||||
case 'recipes':
|
||||
navigate('/recipes');
|
||||
break;
|
||||
case 'permission':
|
||||
navigate('/permission', { state: options });
|
||||
break;
|
||||
case 'ConfigureProviders':
|
||||
navigate('/configure-providers');
|
||||
break;
|
||||
case 'sharedSession':
|
||||
navigate('/shared-session', { state: options });
|
||||
break;
|
||||
case 'recipeEditor':
|
||||
navigate('/recipe-editor', { state: options });
|
||||
break;
|
||||
case 'welcome':
|
||||
navigate('/welcome');
|
||||
break;
|
||||
default:
|
||||
navigate('/');
|
||||
}
|
||||
}}
|
||||
viewOptions={viewOptions}
|
||||
/>
|
||||
);
|
||||
return <SettingsView onClose={() => navigate('/')} setView={setView} viewOptions={viewOptions} />;
|
||||
};
|
||||
|
||||
const SessionsRoute = () => {
|
||||
const navigate = useNavigate();
|
||||
const setView = createNavigationHandler(navigate);
|
||||
|
||||
return (
|
||||
<SessionsView
|
||||
setView={(view: View, options?: ViewOptions) => {
|
||||
// Convert view to route navigation
|
||||
switch (view) {
|
||||
case 'chat':
|
||||
navigate('/', { state: options });
|
||||
break;
|
||||
case 'pair':
|
||||
navigate('/pair', { state: options });
|
||||
break;
|
||||
case 'settings':
|
||||
navigate('/settings', { state: options });
|
||||
break;
|
||||
case 'sessions':
|
||||
navigate('/sessions');
|
||||
break;
|
||||
case 'schedules':
|
||||
navigate('/schedules');
|
||||
break;
|
||||
case 'recipes':
|
||||
navigate('/recipes');
|
||||
break;
|
||||
case 'permission':
|
||||
navigate('/permission', { state: options });
|
||||
break;
|
||||
case 'ConfigureProviders':
|
||||
navigate('/configure-providers');
|
||||
break;
|
||||
case 'sharedSession':
|
||||
navigate('/shared-session', { state: options });
|
||||
break;
|
||||
case 'recipeEditor':
|
||||
navigate('/recipe-editor', { state: options });
|
||||
break;
|
||||
case 'welcome':
|
||||
navigate('/welcome');
|
||||
break;
|
||||
default:
|
||||
navigate('/');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
return <SessionsView setView={setView} />;
|
||||
};
|
||||
|
||||
const SchedulesRoute = () => {
|
||||
@@ -548,6 +332,7 @@ const SharedSessionRouteWrapper = ({
|
||||
}) => {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const setView = createNavigationHandler(navigate);
|
||||
|
||||
const historyState = window.history.state;
|
||||
const sessionDetails = (location.state?.sessionDetails ||
|
||||
@@ -565,47 +350,7 @@ const SharedSessionRouteWrapper = ({
|
||||
if (shareToken && baseUrl) {
|
||||
setIsLoadingSharedSession(true);
|
||||
try {
|
||||
await openSharedSessionFromDeepLink(
|
||||
`goose://sessions/${shareToken}`,
|
||||
(view: View, _options?: SessionLinksViewOptions) => {
|
||||
// Convert view to route navigation
|
||||
switch (view) {
|
||||
case 'chat':
|
||||
navigate('/', { state: _options });
|
||||
break;
|
||||
case 'pair':
|
||||
navigate('/pair', { state: _options });
|
||||
break;
|
||||
case 'settings':
|
||||
navigate('/settings', { state: _options });
|
||||
break;
|
||||
case 'sessions':
|
||||
navigate('/sessions');
|
||||
break;
|
||||
case 'schedules':
|
||||
navigate('/schedules');
|
||||
break;
|
||||
case 'recipes':
|
||||
navigate('/recipes');
|
||||
break;
|
||||
case 'permission':
|
||||
navigate('/permission', { state: _options });
|
||||
break;
|
||||
case 'ConfigureProviders':
|
||||
navigate('/configure-providers');
|
||||
break;
|
||||
case 'sharedSession':
|
||||
navigate('/shared-session', { state: _options });
|
||||
break;
|
||||
case 'recipeEditor':
|
||||
navigate('/recipe-editor', { state: _options });
|
||||
break;
|
||||
default:
|
||||
navigate('/');
|
||||
}
|
||||
},
|
||||
baseUrl
|
||||
);
|
||||
await openSharedSessionFromDeepLink(`goose://sessions/${shareToken}`, setView, baseUrl);
|
||||
} catch (error) {
|
||||
console.error('Failed to retry loading shared session:', error);
|
||||
} finally {
|
||||
@@ -668,7 +413,7 @@ export default function App() {
|
||||
title: 'Pair Chat',
|
||||
messages: [],
|
||||
messageHistoryIndex: 0,
|
||||
recipeConfig: null, // Initialize with no recipe
|
||||
recipeConfig: null,
|
||||
});
|
||||
|
||||
const { getExtensions, addExtension, read } = useConfig();
|
||||
@@ -745,220 +490,24 @@ export default function App() {
|
||||
}
|
||||
initAttemptedRef.current = true;
|
||||
|
||||
console.log(`Initializing app`);
|
||||
const initialize = async () => {
|
||||
const config = window.electron.getConfig();
|
||||
const provider = (await read('GOOSE_PROVIDER', false)) ?? config.GOOSE_DEFAULT_PROVIDER;
|
||||
const model = (await read('GOOSE_MODEL', false)) ?? config.GOOSE_DEFAULT_MODEL;
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const viewType = urlParams.get('view');
|
||||
const resumeSessionId = urlParams.get('resumeSessionId');
|
||||
const recipeConfig = window.appConfig.get('recipe');
|
||||
|
||||
// Check for session resume first - this takes priority over other navigation
|
||||
if (resumeSessionId) {
|
||||
console.log('Session resume detected, letting useChat hook handle navigation');
|
||||
|
||||
// Even when resuming a session, we need to initialize the system
|
||||
const initializeForSessionResume = async () => {
|
||||
try {
|
||||
await initConfig();
|
||||
await readAllConfig({ throwOnError: true });
|
||||
|
||||
const config = window.electron.getConfig();
|
||||
const provider = (await read('GOOSE_PROVIDER', false)) ?? config.GOOSE_DEFAULT_PROVIDER;
|
||||
const model = (await read('GOOSE_MODEL', false)) ?? config.GOOSE_DEFAULT_MODEL;
|
||||
|
||||
if (provider && model) {
|
||||
await initializeSystem(provider as string, model as string, {
|
||||
getExtensions,
|
||||
addExtension,
|
||||
});
|
||||
} else {
|
||||
throw new Error('No provider/model configured for session resume');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize system for session resume:', error);
|
||||
setFatalError(
|
||||
`Failed to initialize system for session resume: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
initializeForSessionResume();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for recipe config - this also needs provider initialization
|
||||
if (recipeConfig && typeof recipeConfig === 'object') {
|
||||
console.log('Recipe deeplink detected, initializing system for recipe');
|
||||
|
||||
const initializeForRecipe = async () => {
|
||||
try {
|
||||
await initConfig();
|
||||
await readAllConfig({ throwOnError: true });
|
||||
|
||||
const config = window.electron.getConfig();
|
||||
const provider = (await read('GOOSE_PROVIDER', false)) ?? config.GOOSE_DEFAULT_PROVIDER;
|
||||
const model = (await read('GOOSE_MODEL', false)) ?? config.GOOSE_DEFAULT_MODEL;
|
||||
|
||||
if (provider && model) {
|
||||
await initializeSystem(provider as string, model as string, {
|
||||
getExtensions,
|
||||
addExtension,
|
||||
});
|
||||
|
||||
// Set up the recipe in pair chat after system is initialized
|
||||
setPairChat((prevChat) => ({
|
||||
...prevChat,
|
||||
recipeConfig: recipeConfig as Recipe,
|
||||
title: (recipeConfig as Recipe)?.title || 'Recipe Chat',
|
||||
messages: [], // Start fresh for recipe
|
||||
messageHistoryIndex: 0,
|
||||
}));
|
||||
|
||||
// Navigate to pair view
|
||||
window.location.hash = '#/pair';
|
||||
window.history.replaceState(
|
||||
{
|
||||
recipeConfig: recipeConfig,
|
||||
resetChat: true,
|
||||
},
|
||||
'',
|
||||
'#/pair'
|
||||
);
|
||||
} else {
|
||||
throw new Error('No provider/model configured for recipe');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize system for recipe:', error);
|
||||
setFatalError(
|
||||
`Failed to initialize system for recipe: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
initializeForRecipe();
|
||||
return;
|
||||
}
|
||||
|
||||
if (viewType) {
|
||||
if (viewType === 'recipeEditor' && recipeConfig) {
|
||||
// Handle recipe editor deep link - use hash routing
|
||||
window.location.hash = '#/recipe-editor';
|
||||
window.history.replaceState({ config: recipeConfig }, '', '#/recipe-editor');
|
||||
} else {
|
||||
// Handle other deep links by redirecting to appropriate route
|
||||
const routeMap: Record<string, string> = {
|
||||
chat: '#/',
|
||||
pair: '#/pair',
|
||||
settings: '#/settings',
|
||||
sessions: '#/sessions',
|
||||
schedules: '#/schedules',
|
||||
recipes: '#/recipes',
|
||||
permission: '#/permission',
|
||||
ConfigureProviders: '#/configure-providers',
|
||||
sharedSession: '#/shared-session',
|
||||
recipeEditor: '#/recipe-editor',
|
||||
welcome: '#/welcome',
|
||||
};
|
||||
|
||||
const route = routeMap[viewType];
|
||||
if (route) {
|
||||
window.location.hash = route;
|
||||
window.history.replaceState({}, '', route);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const initializeApp = async () => {
|
||||
try {
|
||||
// Start cost database initialization early (non-blocking) - only if cost tracking is enabled
|
||||
const costDbPromise = COST_TRACKING_ENABLED
|
||||
? 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();
|
||||
|
||||
try {
|
||||
await readAllConfig({ throwOnError: true });
|
||||
} catch (error) {
|
||||
console.warn('Initial config read failed, attempting recovery:', error);
|
||||
|
||||
const configVersion = localStorage.getItem('configVersion');
|
||||
const shouldMigrateExtensions = !configVersion || parseInt(configVersion, 10) < 3;
|
||||
|
||||
if (shouldMigrateExtensions) {
|
||||
console.log('Performing extension migration...');
|
||||
try {
|
||||
await backupConfig({ throwOnError: true });
|
||||
await initConfig();
|
||||
} catch (migrationError) {
|
||||
console.error('Migration failed:', migrationError);
|
||||
// Continue with recovery attempts
|
||||
}
|
||||
}
|
||||
|
||||
// Try recovery if migration didn't work or wasn't needed
|
||||
console.log('Attempting config recovery...');
|
||||
try {
|
||||
// Try to validate first (faster than recovery)
|
||||
await validateConfig({ throwOnError: true });
|
||||
// If validation passes, try reading again
|
||||
await readAllConfig({ throwOnError: true });
|
||||
} catch {
|
||||
console.log('Config validation failed, attempting recovery...');
|
||||
try {
|
||||
await recoverConfig({ throwOnError: true });
|
||||
await readAllConfig({ throwOnError: true });
|
||||
} catch {
|
||||
console.warn('Config recovery failed, reinitializing...');
|
||||
await initConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const config = window.electron.getConfig();
|
||||
const provider = (await read('GOOSE_PROVIDER', false)) ?? config.GOOSE_DEFAULT_PROVIDER;
|
||||
const model = (await read('GOOSE_MODEL', false)) ?? config.GOOSE_DEFAULT_MODEL;
|
||||
|
||||
if (provider && model) {
|
||||
try {
|
||||
// Initialize system in parallel with cost database (if enabled)
|
||||
const initPromises = [
|
||||
initializeSystem(provider as string, model as string, {
|
||||
getExtensions,
|
||||
addExtension,
|
||||
}),
|
||||
];
|
||||
|
||||
if (COST_TRACKING_ENABLED) {
|
||||
initPromises.push(costDbPromise);
|
||||
}
|
||||
|
||||
await Promise.all(initPromises);
|
||||
} catch (error) {
|
||||
console.error('Error in system initialization:', error);
|
||||
if (error instanceof MalformedConfigError) {
|
||||
throw error;
|
||||
}
|
||||
window.location.hash = '#/';
|
||||
window.history.replaceState({}, '', '#/');
|
||||
}
|
||||
} else {
|
||||
window.location.hash = '#/';
|
||||
window.history.replaceState({}, '', '#/');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fatal error during initialization:', error);
|
||||
setFatalError(error instanceof Error ? error.message : 'Unknown error occurred');
|
||||
}
|
||||
await initializeApp({
|
||||
getExtensions,
|
||||
addExtension,
|
||||
setPairChat,
|
||||
provider: provider as string,
|
||||
model: model as string,
|
||||
});
|
||||
};
|
||||
|
||||
initializeApp();
|
||||
initialize().catch((error) => {
|
||||
console.error('Fatal error during initialization:', error);
|
||||
setFatalError(error instanceof Error ? error.message : 'Unknown error occurred');
|
||||
});
|
||||
}, [getExtensions, addExtension, read, setPairChat]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -975,7 +524,7 @@ export default function App() {
|
||||
|
||||
// Handle navigation to pair view for recipe deeplinks after router is ready
|
||||
useEffect(() => {
|
||||
const recipeConfig = window.appConfig.get('recipe');
|
||||
const recipeConfig = window.appConfig?.get('recipe');
|
||||
if (
|
||||
recipeConfig &&
|
||||
typeof recipeConfig === 'object' &&
|
||||
@@ -1028,7 +577,7 @@ export default function App() {
|
||||
return () => {
|
||||
window.electron.off('open-shared-session', handleOpenSharedSession);
|
||||
};
|
||||
}, [setSharedSessionError]);
|
||||
}, []);
|
||||
|
||||
// Handle recipe decode events from main process
|
||||
useEffect(() => {
|
||||
@@ -1094,7 +643,7 @@ export default function App() {
|
||||
if ((isMac ? event.metaKey : event.ctrlKey) && event.key === 'n') {
|
||||
event.preventDefault();
|
||||
try {
|
||||
const workingDir = window.appConfig.get('GOOSE_WORKING_DIR');
|
||||
const workingDir = window.appConfig?.get('GOOSE_WORKING_DIR');
|
||||
console.log(`Creating new chat window with working dir: ${workingDir}`);
|
||||
window.electron.createChatWindow(undefined, workingDir as string);
|
||||
} catch (error) {
|
||||
@@ -1111,8 +660,7 @@ export default function App() {
|
||||
// Prevent default drag and drop behavior globally to avoid opening files in new windows
|
||||
// but allow our React components to handle drops in designated areas
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const preventDefaults = (e: any) => {
|
||||
const preventDefaults = (e: globalThis.DragEvent) => {
|
||||
// Only prevent default if we're not over a designated drop zone
|
||||
const target = e.target as HTMLElement;
|
||||
const isOverDropZone = target.closest('[data-drop-zone="true"]') !== null;
|
||||
@@ -1123,15 +671,13 @@ export default function App() {
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const handleDragOver = (e: any) => {
|
||||
const handleDragOver = (e: globalThis.DragEvent) => {
|
||||
// Always prevent default for dragover to allow dropping
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const handleDrop = (e: any) => {
|
||||
const handleDrop = (e: globalThis.DragEvent) => {
|
||||
// Only prevent default if we're not over a designated drop zone
|
||||
const target = e.target as HTMLElement;
|
||||
const isOverDropZone = target.closest('[data-drop-zone="true"]') !== null;
|
||||
@@ -1157,10 +703,9 @@ export default function App() {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('Setting up fatal error handler');
|
||||
const handleFatalError = (_event: IpcRendererEvent, ...args: unknown[]) => {
|
||||
const errorMessage = args[0] as string;
|
||||
console.error('Encountered a fatal error: ', errorMessage);
|
||||
console.error('Encountered a fatal error:', errorMessage);
|
||||
console.error('Is loading session:', isLoadingSession);
|
||||
setFatalError(errorMessage);
|
||||
};
|
||||
@@ -1171,7 +716,6 @@ export default function App() {
|
||||
}, [isLoadingSession]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('Setting up view change handler');
|
||||
const handleSetView = (_event: IpcRendererEvent, ...args: unknown[]) => {
|
||||
const newView = args[0] as View;
|
||||
const section = args[1] as string | undefined;
|
||||
@@ -1232,7 +776,7 @@ export default function App() {
|
||||
try {
|
||||
const allowedCommands = await window.electron.getAllowedExtensions();
|
||||
if (allowedCommands && allowedCommands.length > 0) {
|
||||
const isCommandAllowed = allowedCommands.some((allowedCmd) =>
|
||||
const isCommandAllowed = allowedCommands.some((allowedCmd: string) =>
|
||||
command.startsWith(allowedCmd)
|
||||
);
|
||||
if (!isCommandAllowed) {
|
||||
@@ -1337,12 +881,13 @@ export default function App() {
|
||||
return <ErrorUI error={new Error(fatalError)} />;
|
||||
}
|
||||
|
||||
if (isLoadingSession)
|
||||
if (isLoadingSession) {
|
||||
return (
|
||||
<div className="flex justify-center items-center py-12">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-textStandard"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DraftProvider>
|
||||
@@ -1407,7 +952,7 @@ export default function App() {
|
||||
chat={pairChat}
|
||||
setChat={setPairChat}
|
||||
contextKey={`pair-${pairChat.id}`}
|
||||
key={pairChat.id} // Add key prop to force re-render when chat ID changes
|
||||
key={pairChat.id}
|
||||
>
|
||||
<PairRouteWrapper
|
||||
chat={pairChat}
|
||||
@@ -1492,7 +1037,7 @@ export default function App() {
|
||||
</div>
|
||||
{isGoosehintsModalOpen && (
|
||||
<GoosehintsModal
|
||||
directory={window.appConfig.get('GOOSE_WORKING_DIR') as string}
|
||||
directory={window.appConfig?.get('GOOSE_WORKING_DIR') as string}
|
||||
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user