/**
* Root Application Component
* Handles routing and global providers
*/
import { Routes, Route, useNavigate, useLocation } from 'react-router-dom';
import { Component, useEffect } from 'react';
import type { ErrorInfo, ReactNode } from 'react';
import { Toaster } from 'sonner';
import i18n from './i18n';
import { MainLayout } from './components/layout/MainLayout';
import { TooltipProvider } from '@/components/ui/tooltip';
import { Models } from './pages/Models';
import { Chat } from './pages/Chat';
import { Agents } from './pages/Agents';
import { Channels } from './pages/Channels';
import { Skills } from './pages/Skills';
import { Cron } from './pages/Cron';
import { Settings } from './pages/Settings';
import { Setup } from './pages/Setup';
import { useSettingsStore } from './stores/settings';
import { useGatewayStore } from './stores/gateway';
import { applyGatewayTransportPreference } from './lib/api-client';
/**
* Error Boundary to catch and display React rendering errors
*/
class ErrorBoundary extends Component<
{ children: ReactNode },
{ hasError: boolean; error: Error | null }
> {
constructor(props: { children: ReactNode }) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error) {
return { hasError: true, error };
}
componentDidCatch(error: Error, info: ErrorInfo) {
console.error('React Error Boundary caught error:', error, info);
}
render() {
if (this.state.hasError) {
return (
Something went wrong
{this.state.error?.message}
{'\n\n'}
{this.state.error?.stack}
);
}
return this.props.children;
}
}
function App() {
const navigate = useNavigate();
const location = useLocation();
const initSettings = useSettingsStore((state) => state.init);
const theme = useSettingsStore((state) => state.theme);
const language = useSettingsStore((state) => state.language);
const setupComplete = useSettingsStore((state) => state.setupComplete);
const initGateway = useGatewayStore((state) => state.init);
useEffect(() => {
initSettings();
}, [initSettings]);
// Sync i18n language with persisted settings on mount
useEffect(() => {
if (language && language !== i18n.language) {
i18n.changeLanguage(language);
}
}, [language]);
// Initialize Gateway connection on mount
useEffect(() => {
initGateway();
}, [initGateway]);
// Redirect to setup wizard if not complete
useEffect(() => {
if (!setupComplete && !location.pathname.startsWith('/setup')) {
navigate('/setup');
}
}, [setupComplete, location.pathname, navigate]);
// Listen for navigation events from main process
useEffect(() => {
const handleNavigate = (...args: unknown[]) => {
const path = args[0];
if (typeof path === 'string') {
navigate(path);
}
};
const unsubscribe = window.electron.ipcRenderer.on('navigate', handleNavigate);
return () => {
if (typeof unsubscribe === 'function') {
unsubscribe();
}
};
}, [navigate]);
// Apply theme
useEffect(() => {
const root = window.document.documentElement;
root.classList.remove('light', 'dark');
if (theme === 'system') {
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
root.classList.add(systemTheme);
} else {
root.classList.add(theme);
}
}, [theme]);
useEffect(() => {
applyGatewayTransportPreference();
}, []);
return (
{/* Setup wizard (shown on first launch) */}
} />
{/* Main application routes */}
}>
} />
} />
} />
} />
} />
} />
} />
{/* Global toast notifications */}
);
}
export default App;