jan/web-app/src/containers/dialogs/CortexFailureDialog.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

88 lines
2.5 KiB
TypeScript

import { useEffect, useState } from 'react'
import { listen } from '@tauri-apps/api/event'
import { invoke } from '@tauri-apps/api/core'
import { t } from 'i18next'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'
export function CortexFailureDialog() {
const [showDialog, setShowDialog] = useState(false)
useEffect(() => {
let unlisten: (() => void) | undefined
const setupListener = async () => {
unlisten = await listen<null>(
'cortex_max_restarts_reached',
(event) => {
console.log('Cortex max restarts reached event received:', event)
setShowDialog(true)
}
)
}
setupListener()
return () => {
if (unlisten) {
unlisten()
}
}
}, [])
const handleRestartJan = async () => {
try {
await invoke('relaunch')
} catch (error) {
console.error('Failed to relaunch app:', error)
alert(
'Failed to automatically restart. Please close and reopen Jan manually.'
)
}
}
if (!showDialog) {
return null
}
return (
<Dialog open={showDialog} onOpenChange={setShowDialog}>
<DialogContent>
<DialogHeader>
<DialogTitle>{t('cortexFailureDialog.title', 'Local AI Engine Issue')}</DialogTitle>
</DialogHeader>
<DialogDescription>
{t('cortexFailureDialog.description', 'The local AI engine (Cortex) failed to start after multiple attempts. This might prevent some features from working correctly.')}
</DialogDescription>
<DialogFooter className="gap-2 sm:gap-0">
<Button
variant="default"
className="bg-transparent border border-main-view-fg/20 hover:bg-main-view-fg/10"
onClick={() => {
window.open('https://jan.ai/support', '_blank')
setShowDialog(false)
}}
>
{t('cortexFailureDialog.contactSupport', 'Contact Support')}
</Button>
<Button
variant="default"
className="bg-transparent border border-main-view-fg/20 hover:bg-main-view-fg/10"
onClick={handleRestartJan}
>
{t('cortexFailureDialog.restartJan', 'Restart Jan')}
</Button>
<Button onClick={() => setShowDialog(false)}>
{t('common.okay', 'Okay')}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}