jan/web-app/src/routes/__root.tsx
Sam Hoang Van 7df7d8ffa0
feat: Implement Cortex server auto-restart and webview notification (#5074)
* feat: Implement Cortex server auto-restart and webview notification

Implements a robust auto-restart mechanism for the Cortex server (sidecar)
managed by the Tauri backend.

Key changes:

Backend (src-tauri):
- Modified `core/setup.rs` to:
  - Loop sidecar spawning, attempting up to `MAX_RESTARTS` (5) times with a
    `RESTART_DELAY_MS` (5 seconds) between attempts.
  - Monitor the sidecar process for unexpected termination (crashes or
    non-zero exit codes).
  - Reset the restart attempt count to 0 in `AppState` upon a successful
    server spawn.
  - Emit a "cortex_max_restarts_reached" event to the webview if the
    server fails to start after `MAX_RESTARTS`.
- Updated `core/state.rs` to include `cortex_restart_count: Arc<Mutex<u32>>`
  in `AppState` to track restart attempts.
- Added a new Tauri command `reset_cortex_restart_count` in `core/cmd.rs`
  to allow the webview (or other parts of the app) to reset this counter.
- Registered the new command and initialized the `cortex_restart_count`
  in `lib.rs`.

Frontend (web-app):
- Created a new component `CortexFailureDialog.tsx` in
  `src/containers/dialogs/` to:
  - Listen for the "cortex_max_restarts_reached" event from Tauri.
  - Display a dialog informing the user that the local AI engine (Cortex)
    failed to start after multiple attempts.
  - Offer options to "Contact Support" (opens jan.ai/support),
    "Restart Jan" (invokes the `relaunch` Tauri command), or "Okay"
    (dismisses the dialog).
- Integrated the `CortexFailureDialog` into the `RootLayout` in
  `src/routes/__root.tsx` so it's globally available.
- Corrected button variants in `__root.tsx` to use `variant="default"`
  with appropriate classNames for outline styling, resolving TypeScript
  errors.

* refactor: Improve async handling and logging in setup_sidecar function
2025-05-22 23:09:43 +07:00

83 lines
2.7 KiB
TypeScript

import { createRootRoute, Outlet, useRouterState } from '@tanstack/react-router'
// import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
import LeftPanel from '@/containers/LeftPanel'
import DialogAppUpdater from '@/containers/dialogs/AppUpdater'
import { CortexFailureDialog } from '@/containers/dialogs/CortexFailureDialog' // Added import
import { Fragment } from 'react/jsx-runtime'
import { AppearanceProvider } from '@/providers/AppearanceProvider'
import { ThemeProvider } from '@/providers/ThemeProvider'
import { KeyboardShortcutsProvider } from '@/providers/KeyboardShortcuts'
import { DataProvider } from '@/providers/DataProvider'
import { route } from '@/constants/routes'
import { ExtensionProvider } from '@/providers/ExtensionProvider'
import { ToasterProvider } from '@/providers/ToasterProvider'
export const Route = createRootRoute({
component: RootLayout,
})
const AppLayout = () => {
return (
<Fragment>
<KeyboardShortcutsProvider />
<main className="relative h-svh text-sm antialiased select-none bg-app">
{/* Fake absolute panel top to enable window drag */}
<div className="absolute w-full h-10 z-10" data-tauri-drag-region />
<DialogAppUpdater />
<div className="flex h-full">
{/* left content panel - only show if not logs route */}
<LeftPanel />
{/* Main content panel */}
<div className="h-full flex w-full p-1">
<div className="bg-main-view text-main-view-fg border border-main-view-fg/5 w-full rounded-lg overflow-hidden">
<Outlet />
</div>
</div>
</div>
</main>
</Fragment>
)
}
const LogsLayout = () => {
return (
<Fragment>
<main className="relative h-svh text-sm antialiased select-text bg-app">
<div className="flex h-full">
{/* Main content panel */}
<div className="h-full flex w-full">
<div className="bg-main-view text-main-view-fg border border-main-view-fg/5 w-full overflow-hidden">
<Outlet />
</div>
</div>
</div>
</main>
</Fragment>
)
}
function RootLayout() {
const router = useRouterState()
const isLocalAPIServerLogsRoute =
router.location.pathname === route.localApiServerlogs ||
router.location.pathname === route.systemMonitor ||
router.location.pathname === route.appLogs
return (
<Fragment>
<ThemeProvider />
<AppearanceProvider />
<ToasterProvider />
<ExtensionProvider>
<DataProvider />
</ExtensionProvider>
{isLocalAPIServerLogsRoute ? <LogsLayout /> : <AppLayout />}
{/* <TanStackRouterDevtools position="bottom-right" /> */}
<CortexFailureDialog />
</Fragment>
)
}