File size: 5,441 Bytes
3d700dd | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | import React from "react";
import {
useRouteError,
isRouteErrorResponse,
Outlet,
useLocation,
} from "react-router";
import { useTranslation } from "react-i18next";
import { I18nKey } from "#/i18n/declaration";
import i18n from "#/i18n";
import { useConfig } from "#/hooks/query/use-config";
import { Sidebar } from "#/components/features/sidebar/sidebar";
import { SidebarMobileNavProvider } from "#/components/features/sidebar/sidebar-mobile-nav-context";
import { SidebarMobileMenuBar } from "#/components/features/sidebar/sidebar-mobile-menu-bar";
import { useSettings } from "#/hooks/query/use-settings";
import { useEnsureActiveProfile } from "#/hooks/use-ensure-active-profile";
import { useMigrateEnabledSkills } from "#/hooks/use-migrate-enabled-skills";
import { useSyncTelemetryConsent } from "#/hooks/use-sync-telemetry-consent";
import { useSyncAutomationTelemetryConsent } from "#/hooks/use-sync-automation-telemetry-consent";
import { useTelemetryIdentity } from "#/hooks/use-telemetry-identity";
import { LoadingSpinner } from "#/components/shared/loading-spinner";
import { useAppTitle } from "#/hooks/use-app-title";
import { ReactRouterNavigationProvider } from "./react-router-navigation-provider";
import { OnboardingHost } from "#/components/features/onboarding";
import { isOnboardingPreviewActive } from "#/components/features/onboarding/onboarding-preview";
import { CanvasExtensionsRuntimeProvider } from "#/components/features/canvas-extensions/canvas-extensions-runtime";
const EnvironmentSwitchOverlay = React.lazy(
() => import("#/components/features/backends/environment-switch-overlay"),
);
const AlertBanner = React.lazy(() =>
import("#/components/features/alerts/alert-banner").then((m) => ({
default: m.AlertBanner,
})),
);
const CommandMenu = React.lazy(() =>
import("#/components/features/command-menu/command-menu").then((m) => ({
default: m.CommandMenu,
})),
);
export function ErrorBoundary() {
const error = useRouteError();
const { t } = useTranslation("openhands");
if (isRouteErrorResponse(error)) {
return (
<div>
<h1>{error.status}</h1>
<p>{error.statusText}</p>
<pre>
{error.data instanceof Object
? JSON.stringify(error.data)
: error.data}
</pre>
</div>
);
}
if (error instanceof Error) {
return (
<div>
<h1>{t(I18nKey.ERROR$GENERIC)}</h1>
<pre>{error.message}</pre>
</div>
);
}
return (
<div>
<h1>{t(I18nKey.ERROR$UNKNOWN)}</h1>
</div>
);
}
export default function MainApp() {
const location = useLocation();
const appTitle = useAppTitle();
const { data: settings } = useSettings();
const config = useConfig();
useSyncAutomationTelemetryConsent();
useSyncTelemetryConsent();
useTelemetryIdentity();
// Local-mode policy: keep a profile active so a usable LLM is always selected.
useEnsureActiveProfile();
// One-shot move from the catalog deny-list to an explicit allow-list.
useMigrateEnabledSkills();
React.useEffect(() => {
if (settings?.language) {
i18n.changeLanguage(settings.language);
}
}, [settings?.language]);
if (config.isLoading) {
return (
<div className="min-h-screen flex items-center justify-center bg-base">
<LoadingSpinner size="large" />
</div>
);
}
// Conversation + full-screen panel routes put the mobile menu control in the
// chat / panel header; omit the extra top row so we don't duplicate chrome.
const hideMobileSidebarMenuBar = /^\/conversations\/[^/]+/.test(
location.pathname,
);
const showOnboardingPreview = isOnboardingPreviewActive(location.search);
return (
<ReactRouterNavigationProvider>
<CanvasExtensionsRuntimeProvider>
<SidebarMobileNavProvider>
<div
data-testid="root-layout"
className="h-screen lg:min-w-5xl flex flex-col md:flex-row bg-base overflow-hidden p-0"
>
<title>{appTitle}</title>
<Sidebar />
<div className="flex min-h-0 flex-col w-full min-w-0 h-full gap-3">
{!hideMobileSidebarMenuBar ? <SidebarMobileMenuBar /> : null}
{config.data &&
(config.data.maintenance_start_time ||
(config.data.faulty_models &&
config.data.faulty_models.length > 0) ||
config.data.error_message) && (
<React.Suspense fallback={null}>
<AlertBanner
maintenanceStartTime={config.data.maintenance_start_time}
faultyModels={config.data.faulty_models}
errorMessage={config.data.error_message}
updatedAt={config.data.updated_at}
/>
</React.Suspense>
)}
<div
id="root-outlet"
className="relative flex-1 overflow-auto px-0 custom-scrollbar"
>
<Outlet />
</div>
</div>
</div>
<React.Suspense fallback={null}>
<EnvironmentSwitchOverlay />
<CommandMenu />
</React.Suspense>
{showOnboardingPreview ? <OnboardingHost /> : null}
</SidebarMobileNavProvider>
</CanvasExtensionsRuntimeProvider>
</ReactRouterNavigationProvider>
);
}
|