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>
  );
}