feat: more robust client initialization for the app (#3830)
This commit is contained in:
@@ -19,7 +19,6 @@ import type {
|
|||||||
ExtensionConfig,
|
ExtensionConfig,
|
||||||
} from '../api';
|
} from '../api';
|
||||||
import { removeShims } from './settings/extensions/utils';
|
import { removeShims } from './settings/extensions/utils';
|
||||||
import { ensureClientInitialized } from '../utils';
|
|
||||||
|
|
||||||
export type { ExtensionConfig } from '../api/types.gen';
|
export type { ExtensionConfig } from '../api/types.gen';
|
||||||
|
|
||||||
@@ -183,7 +182,6 @@ export const ConfigProvider: React.FC<ConfigProviderProps> = ({ children }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Load all configuration data and providers on mount
|
// Load all configuration data and providers on mount
|
||||||
(async () => {
|
(async () => {
|
||||||
await ensureClientInitialized();
|
|
||||||
// Load config
|
// Load config
|
||||||
const configResponse = await readAllConfig();
|
const configResponse = await readAllConfig();
|
||||||
setConfig(configResponse.data?.config || {});
|
setConfig(configResponse.data?.config || {});
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
getModelDisplayName,
|
getModelDisplayName,
|
||||||
getProviderDisplayName,
|
getProviderDisplayName,
|
||||||
} from './settings/models/predefinedModelsUtils';
|
} from './settings/models/predefinedModelsUtils';
|
||||||
import { ensureClientInitialized } from '../utils';
|
|
||||||
|
|
||||||
// titles
|
// titles
|
||||||
export const UNKNOWN_PROVIDER_TITLE = 'Provider name lookup';
|
export const UNKNOWN_PROVIDER_TITLE = 'Provider name lookup';
|
||||||
@@ -171,7 +170,6 @@ export const ModelAndProviderProvider: React.FC<ModelAndProviderProviderProps> =
|
|||||||
|
|
||||||
const refreshCurrentModelAndProvider = useCallback(async () => {
|
const refreshCurrentModelAndProvider = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
await ensureClientInitialized();
|
|
||||||
const { model, provider } = await getCurrentModelAndProvider();
|
const { model, provider } = await getCurrentModelAndProvider();
|
||||||
setCurrentModel(model);
|
setCurrentModel(model);
|
||||||
setCurrentProvider(provider);
|
setCurrentProvider(provider);
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { startOpenRouterSetup } from '../utils/openRouterSetup';
|
|||||||
import WelcomeGooseLogo from './WelcomeGooseLogo';
|
import WelcomeGooseLogo from './WelcomeGooseLogo';
|
||||||
import { initializeSystem } from '../utils/providerUtils';
|
import { initializeSystem } from '../utils/providerUtils';
|
||||||
import { toastService } from '../toasts';
|
import { toastService } from '../toasts';
|
||||||
import { ensureClientInitialized } from '../utils';
|
|
||||||
|
|
||||||
interface ProviderGuardProps {
|
interface ProviderGuardProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@@ -96,8 +95,6 @@ export default function ProviderGuard({ children }: ProviderGuardProps) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkProvider = async () => {
|
const checkProvider = async () => {
|
||||||
try {
|
try {
|
||||||
await ensureClientInitialized();
|
|
||||||
|
|
||||||
const config = window.electron.getConfig();
|
const config = window.electron.getConfig();
|
||||||
console.log('ProviderGuard - Full config:', config);
|
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}</>;
|
||||||
|
};
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { ChatType } from '../types/chat';
|
import { ChatType } from '../types/chat';
|
||||||
import { fetchSessionDetails, generateSessionId } from '../sessions';
|
import { fetchSessionDetails, generateSessionId } from '../sessions';
|
||||||
import { ensureClientInitialized } from '../utils';
|
|
||||||
import { View, ViewOptions } from '../App';
|
import { View, ViewOptions } from '../App';
|
||||||
|
|
||||||
type UseChatArgs = {
|
type UseChatArgs = {
|
||||||
@@ -30,7 +29,6 @@ export const useChat = ({ setIsLoadingSession, setView, setPairChat }: UseChatAr
|
|||||||
|
|
||||||
setIsLoadingSession(true);
|
setIsLoadingSession(true);
|
||||||
try {
|
try {
|
||||||
await ensureClientInitialized();
|
|
||||||
const sessionDetails = await fetchSessionDetails(resumeSessionId);
|
const sessionDetails = await fetchSessionDetails(resumeSessionId);
|
||||||
|
|
||||||
// Only set view if we have valid session details
|
// Only set view if we have valid session details
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import React, { Suspense, lazy } from 'react';
|
import React, { Suspense, lazy } from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import { ConfigProvider } from './components/ConfigContext';
|
import { ConfigProvider } from './components/ConfigContext';
|
||||||
|
import {
|
||||||
|
ClientInitializationProvider,
|
||||||
|
RequireClientInitialization,
|
||||||
|
} from './contexts/ClientInitializationContext';
|
||||||
import { ErrorBoundary } from './components/ErrorBoundary';
|
import { ErrorBoundary } from './components/ErrorBoundary';
|
||||||
import { patchConsoleLogging } from './utils';
|
import { patchConsoleLogging } from './utils';
|
||||||
import SuspenseLoader from './suspense-loader';
|
import SuspenseLoader from './suspense-loader';
|
||||||
@@ -10,13 +14,17 @@ patchConsoleLogging();
|
|||||||
const App = lazy(() => import('./App'));
|
const App = lazy(() => import('./App'));
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
<React.StrictMode>
|
<ClientInitializationProvider>
|
||||||
<Suspense fallback={SuspenseLoader()}>
|
<React.StrictMode>
|
||||||
<ConfigProvider>
|
<Suspense fallback={SuspenseLoader()}>
|
||||||
<ErrorBoundary>
|
<RequireClientInitialization>
|
||||||
<App />
|
<ConfigProvider>
|
||||||
</ErrorBoundary>
|
<ErrorBoundary>
|
||||||
</ConfigProvider>
|
<App />
|
||||||
</Suspense>
|
</ErrorBoundary>
|
||||||
</React.StrictMode>
|
</ConfigProvider>
|
||||||
|
</RequireClientInitialization>
|
||||||
|
</Suspense>
|
||||||
|
</React.StrictMode>
|
||||||
|
</ClientInitializationProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { clsx, type ClassValue } from 'clsx';
|
import { clsx, type ClassValue } from 'clsx';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
import { client } from './api/client.gen';
|
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs));
|
return twMerge(clsx(inputs));
|
||||||
@@ -17,19 +16,3 @@ export function patchConsoleLogging() {
|
|||||||
// Intercept console methods
|
// Intercept console methods
|
||||||
return;
|
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;
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user