* 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
88 lines
2.5 KiB
TypeScript
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>
|
|
)
|
|
} |