From 469b9aa9c6cbcd5705487a43ec2047309b065e4a Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Wed, 10 Jun 2026 20:31:35 -0400 Subject: [PATCH] feat: in-app update checker + Spinner import for General settings - Add useTauriUpdater hook (check_for_update / install_update commands) - Add AppUpdates section to General settings (Tauri-only, hidden on web) Co-Authored-By: Claude Sonnet 4.6 --- src/app/features/settings/general/General.tsx | 43 +++++++++++++++++ src/app/hooks/useTauriUpdater.ts | 47 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 src/app/hooks/useTauriUpdater.ts diff --git a/src/app/features/settings/general/General.tsx b/src/app/features/settings/general/General.tsx index 8a83441c8..89b9922be 100644 --- a/src/app/features/settings/general/General.tsx +++ b/src/app/features/settings/general/General.tsx @@ -26,6 +26,7 @@ import { PopOut, RectCords, Scroll, + Spinner, Switch, Text, toRem, @@ -63,6 +64,7 @@ import { useMessageLayoutItems } from '../../../hooks/useMessageLayout'; import { useMessageSpacingItems } from '../../../hooks/useMessageSpacing'; import { useDateFormatItems } from '../../../hooks/useDateFormat'; import { SequenceCardStyle } from '../styles.css'; +import { useTauriUpdater } from '../../../hooks/useTauriUpdater'; type ThemeSelectorProps = { themeNames: Record; @@ -1476,6 +1478,46 @@ function Messages() { ); } +function AppUpdates() { + const { isTauri, status, check, install } = useTauriUpdater(); + if (!isTauri) return null; + + const description = + status.state === 'checking' + ? 'Checking for updates...' + : status.state === 'up-to-date' + ? 'Lotus Chat is up to date.' + : status.state === 'available' + ? `Update available: v${status.version}` + : status.state === 'installing' + ? 'Installing update, the app will restart shortly...' + : status.state === 'error' + ? `Update check failed: ${status.message}` + : 'Check for a new version of Lotus Chat.'; + + const after = + status.state === 'available' ? ( + + ) : status.state === 'checking' || status.state === 'installing' ? ( + + ) : ( + + ); + + return ( + + App Updates + + + + + ); +} + type GeneralProps = { requestClose: () => void; }; @@ -1506,6 +1548,7 @@ export function General({ requestClose }: GeneralProps) { + diff --git a/src/app/hooks/useTauriUpdater.ts b/src/app/hooks/useTauriUpdater.ts new file mode 100644 index 000000000..0f649bf2f --- /dev/null +++ b/src/app/hooks/useTauriUpdater.ts @@ -0,0 +1,47 @@ +import { useState, useCallback } from 'react'; + +type TauriInternals = { invoke: (cmd: string, args?: Record) => Promise }; +const tauriInvoke = (): TauriInternals['invoke'] | undefined => + (window as unknown as { __TAURI_INTERNALS__?: TauriInternals }).__TAURI_INTERNALS__?.invoke; + +type UpdateStatus = + | { state: 'idle' } + | { state: 'checking' } + | { state: 'up-to-date' } + | { state: 'available'; version: string } + | { state: 'installing' } + | { state: 'error'; message: string }; + +export function useTauriUpdater() { + const isTauri = !!tauriInvoke(); + const [status, setStatus] = useState({ state: 'idle' }); + + const check = useCallback(async () => { + const invoke = tauriInvoke(); + if (!invoke) return; + setStatus({ state: 'checking' }); + try { + const result = (await invoke('check_for_update')) as { available: boolean; version?: string }; + setStatus( + result.available && result.version + ? { state: 'available', version: result.version } + : { state: 'up-to-date' }, + ); + } catch (e) { + setStatus({ state: 'error', message: String(e) }); + } + }, []); + + const install = useCallback(async () => { + const invoke = tauriInvoke(); + if (!invoke) return; + setStatus({ state: 'installing' }); + try { + await invoke('install_update'); + } catch (e) { + setStatus({ state: 'error', message: String(e) }); + } + }, []); + + return { isTauri, status, check, install }; +}