feat: more robust client initialization for the app (#3830)

This commit is contained in:
Alex Hancock
2025-08-04 12:56:47 -04:00
committed by GitHub
parent 162011fccb
commit 3f567efd9e
7 changed files with 101 additions and 35 deletions
@@ -19,7 +19,6 @@ import type {
ExtensionConfig,
} from '../api';
import { removeShims } from './settings/extensions/utils';
import { ensureClientInitialized } from '../utils';
export type { ExtensionConfig } from '../api/types.gen';
@@ -183,7 +182,6 @@ export const ConfigProvider: React.FC<ConfigProviderProps> = ({ children }) => {
useEffect(() => {
// Load all configuration data and providers on mount
(async () => {
await ensureClientInitialized();
// Load config
const configResponse = await readAllConfig();
setConfig(configResponse.data?.config || {});
@@ -8,7 +8,6 @@ import {
getModelDisplayName,
getProviderDisplayName,
} from './settings/models/predefinedModelsUtils';
import { ensureClientInitialized } from '../utils';
// titles
export const UNKNOWN_PROVIDER_TITLE = 'Provider name lookup';
@@ -171,7 +170,6 @@ export const ModelAndProviderProvider: React.FC<ModelAndProviderProviderProps> =
const refreshCurrentModelAndProvider = useCallback(async () => {
try {
await ensureClientInitialized();
const { model, provider } = await getCurrentModelAndProvider();
setCurrentModel(model);
setCurrentProvider(provider);
@@ -6,7 +6,6 @@ import { startOpenRouterSetup } from '../utils/openRouterSetup';
import WelcomeGooseLogo from './WelcomeGooseLogo';
import { initializeSystem } from '../utils/providerUtils';
import { toastService } from '../toasts';
import { ensureClientInitialized } from '../utils';
interface ProviderGuardProps {
children: React.ReactNode;
@@ -96,8 +95,6 @@ export default function ProviderGuard({ children }: ProviderGuardProps) {
useEffect(() => {
const checkProvider = async () => {
try {
await ensureClientInitialized();
const config = window.electron.getConfig();
console.log('ProviderGuard - Full config:', config);
@@ -0,0 +1,84 @@
import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react';
import { client } from '../api/client.gen';
interface ClientInitializationContextType {
isInitialized: boolean;
initializationError: Error | null;
}
// Track if client has been initialized to avoid duplicate initialization
let clientInitialized = false;
async function ensureClientInitialized() {
if (clientInitialized) return;
client.setConfig({
baseUrl: window.appConfig.get('GOOSE_API_HOST') + ':' + window.appConfig.get('GOOSE_PORT'),
headers: {
'Content-Type': 'application/json',
'X-Secret-Key': await window.electron.getSecretKey(),
},
});
clientInitialized = true;
}
const ClientInitializationContext = createContext<ClientInitializationContextType | undefined>(
undefined
);
interface ClientInitializationProviderProps {
children: ReactNode;
}
export const ClientInitializationProvider: React.FC<ClientInitializationProviderProps> = ({
children,
}) => {
const [isInitialized, setIsInitialized] = useState(false);
const [initializationError, setInitializationError] = useState<Error | null>(null);
useEffect(() => {
const initializeClient = async () => {
try {
await ensureClientInitialized();
setIsInitialized(true);
} catch (error) {
console.error('Failed to initialize API client:', error);
setInitializationError(error instanceof Error ? error : new Error('Unknown error'));
}
};
initializeClient();
}, []);
return (
<ClientInitializationContext.Provider value={{ isInitialized, initializationError }}>
{children}
</ClientInitializationContext.Provider>
);
};
export const useClientInitialization = () => {
const context = useContext(ClientInitializationContext);
if (context === undefined) {
throw new Error('useClientInitialization must be used within a ClientInitializationProvider');
}
return context;
};
// Helper component to ensure initialization before rendering children
export const RequireClientInitialization: React.FC<{ children: ReactNode }> = ({ children }) => {
const { isInitialized, initializationError } = useClientInitialization();
if (initializationError) {
throw initializationError;
}
if (!isInitialized) {
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 <>{children}</>;
};
-2
View File
@@ -1,7 +1,6 @@
import { useEffect, useState } from 'react';
import { ChatType } from '../types/chat';
import { fetchSessionDetails, generateSessionId } from '../sessions';
import { ensureClientInitialized } from '../utils';
import { View, ViewOptions } from '../App';
type UseChatArgs = {
@@ -30,7 +29,6 @@ export const useChat = ({ setIsLoadingSession, setView, setPairChat }: UseChatAr
setIsLoadingSession(true);
try {
await ensureClientInitialized();
const sessionDetails = await fetchSessionDetails(resumeSessionId);
// Only set view if we have valid session details
+17 -9
View File
@@ -1,6 +1,10 @@
import React, { Suspense, lazy } from 'react';
import ReactDOM from 'react-dom/client';
import { ConfigProvider } from './components/ConfigContext';
import {
ClientInitializationProvider,
RequireClientInitialization,
} from './contexts/ClientInitializationContext';
import { ErrorBoundary } from './components/ErrorBoundary';
import { patchConsoleLogging } from './utils';
import SuspenseLoader from './suspense-loader';
@@ -10,13 +14,17 @@ patchConsoleLogging();
const App = lazy(() => import('./App'));
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<Suspense fallback={SuspenseLoader()}>
<ConfigProvider>
<ErrorBoundary>
<App />
</ErrorBoundary>
</ConfigProvider>
</Suspense>
</React.StrictMode>
<ClientInitializationProvider>
<React.StrictMode>
<Suspense fallback={SuspenseLoader()}>
<RequireClientInitialization>
<ConfigProvider>
<ErrorBoundary>
<App />
</ErrorBoundary>
</ConfigProvider>
</RequireClientInitialization>
</Suspense>
</React.StrictMode>
</ClientInitializationProvider>
);
-17
View File
@@ -1,6 +1,5 @@
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
import { client } from './api/client.gen';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
@@ -17,19 +16,3 @@ export function patchConsoleLogging() {
// Intercept console methods
return;
}
// This needs to be called before any API calls are made, but since we're using the client
// in multiple useEffect locations, we can't be sure who goes first.
let clientInitialized = false;
export async function ensureClientInitialized() {
if (clientInitialized) return;
client.setConfig({
baseUrl: window.appConfig.get('GOOSE_API_HOST') + ':' + window.appConfig.get('GOOSE_PORT'),
headers: {
'Content-Type': 'application/json',
'X-Secret-Key': await window.electron.getSecretKey(),
},
});
clientInitialized = true;
}