mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-07-19 04:37:28 +08:00
fix(desktop): auto-update install now actually replaces the app
This commit is contained in:
@@ -253,7 +253,14 @@ function setupIPC() {
|
|||||||
setUpdateLanguage(lang)
|
setUpdateLanguage(lang)
|
||||||
startDownload()
|
startDownload()
|
||||||
})
|
})
|
||||||
ipcMain.handle('update-install', () => quitAndInstall())
|
ipcMain.handle('update-install', () => {
|
||||||
|
// Let the window actually close so the app can fully quit — otherwise the
|
||||||
|
// close-to-tray handler preventDefault()s it, the process stays alive, and
|
||||||
|
// Squirrel.Mac can't swap the app bundle (the update silently no-ops and
|
||||||
|
// relaunching still shows the old version).
|
||||||
|
isQuitting = true
|
||||||
|
quitAndInstall()
|
||||||
|
})
|
||||||
|
|
||||||
// Synchronous OS locale lookup (e.g. "zh-CN", "en-US"). Used by the renderer
|
// Synchronous OS locale lookup (e.g. "zh-CN", "en-US"). Used by the renderer
|
||||||
// to pick a sensible default UI language on first run before any paint.
|
// to pick a sensible default UI language on first run before any paint.
|
||||||
|
|||||||
@@ -229,6 +229,16 @@ function attemptDownload(): void {
|
|||||||
export function quitAndInstall(): void {
|
export function quitAndInstall(): void {
|
||||||
if (!app.isPackaged) return
|
if (!app.isPackaged) return
|
||||||
log('quitAndInstall: relaunching to install update')
|
log('quitAndInstall: relaunching to install update')
|
||||||
// isSilent=false (show installer), isForceRunAfter=true (relaunch after).
|
// isSilent MUST be true on Windows: our NSIS installer is an "assisted"
|
||||||
autoUpdater.quitAndInstall(false, true)
|
// installer (oneClick:false + allowToChangeInstallationDirectory), so a
|
||||||
|
// non-silent update re-shows the install-dir / install-mode wizard instead of
|
||||||
|
// updating in place. Silent skips the wizard and updates directly. macOS
|
||||||
|
// (Squirrel.Mac) ignores isSilent, so false there is fine.
|
||||||
|
// isForceRunAfter=true relaunches the app once the install finishes.
|
||||||
|
// Drop window-all-closed handlers first: with an assisted NSIS installer a
|
||||||
|
// lingering handler can keep the process alive and stop the installer from
|
||||||
|
// replacing files / relaunching (a documented electron-updater gotcha).
|
||||||
|
app.removeAllListeners('window-all-closed')
|
||||||
|
const isSilent = process.platform === 'win32'
|
||||||
|
autoUpdater.quitAndInstall(isSilent, true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Download, RefreshCw, X, Loader2 } from 'lucide-react'
|
import { Download, RefreshCw, X, Loader2, AlertTriangle } from 'lucide-react'
|
||||||
import { t } from '../i18n'
|
import { t } from '../i18n'
|
||||||
import { useUpdateStore, hasAvailableUpdate } from '../store/updateStore'
|
import { useUpdateStore, hasAvailableUpdate } from '../store/updateStore'
|
||||||
|
|
||||||
@@ -12,9 +12,12 @@ const UpdateBanner: React.FC = () => {
|
|||||||
|
|
||||||
const available = hasAvailableUpdate(state)
|
const available = hasAvailableUpdate(state)
|
||||||
const status = state.status
|
const status = state.status
|
||||||
|
const errored = status?.state === 'error'
|
||||||
|
|
||||||
// Render nothing when there's no update, or the panel is closed (dismissed).
|
// Show the panel when it's open AND we either know of an update or hit an
|
||||||
if (!available || !open) return null
|
// error. Keeping it up on error is important: a failed download must surface
|
||||||
|
// a visible message instead of silently doing nothing.
|
||||||
|
if (!open || (!available && !errored)) return null
|
||||||
|
|
||||||
const version = state.version
|
const version = state.version
|
||||||
const downloading = status?.state === 'downloading'
|
const downloading = status?.state === 'downloading'
|
||||||
@@ -25,8 +28,12 @@ const UpdateBanner: React.FC = () => {
|
|||||||
<div className="rounded-lg border border-default bg-elevated shadow-lg p-3 space-y-2.5">
|
<div className="rounded-lg border border-default bg-elevated shadow-lg p-3 space-y-2.5">
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<p className="text-[13px] font-semibold text-content">{t('update_available')}</p>
|
<p className="text-[13px] font-semibold text-content">
|
||||||
{version && <p className="text-xs text-content-tertiary mt-0.5">v{version}</p>}
|
{errored ? t('update_failed') : t('update_available')}
|
||||||
|
</p>
|
||||||
|
{!errored && version && (
|
||||||
|
<p className="text-xs text-content-tertiary mt-0.5">v{version}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => state.dismiss()}
|
onClick={() => state.dismiss()}
|
||||||
@@ -37,7 +44,25 @@ const UpdateBanner: React.FC = () => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{downloading && (
|
{errored && (
|
||||||
|
<div className="space-y-2.5">
|
||||||
|
<div className="flex items-start gap-2 text-xs text-content-secondary">
|
||||||
|
<AlertTriangle size={13} className="text-amber-500 flex-shrink-0 mt-0.5" />
|
||||||
|
<span className="break-words">
|
||||||
|
{status?.state === 'error' ? status.message : ''}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => state.download()}
|
||||||
|
className="w-full inline-flex items-center justify-center gap-2 rounded-btn bg-accent text-accent-contrast hover:bg-accent-hover px-3 py-2 text-[13px] font-medium cursor-pointer transition-colors"
|
||||||
|
>
|
||||||
|
<RefreshCw size={15} />
|
||||||
|
{t('update_retry')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!errored && downloading && (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="flex items-center gap-2 text-xs text-content-secondary">
|
<div className="flex items-center gap-2 text-xs text-content-secondary">
|
||||||
<Loader2 size={13} className="animate-spin" />
|
<Loader2 size={13} className="animate-spin" />
|
||||||
@@ -49,7 +74,7 @@ const UpdateBanner: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!downloading && !downloaded && (
|
{!errored && !downloading && !downloaded && (
|
||||||
<button
|
<button
|
||||||
onClick={() => state.download()}
|
onClick={() => state.download()}
|
||||||
className="w-full inline-flex items-center justify-center gap-2 rounded-btn bg-accent text-accent-contrast hover:bg-accent-hover px-3 py-2 text-[13px] font-medium cursor-pointer transition-colors"
|
className="w-full inline-flex items-center justify-center gap-2 rounded-btn bg-accent text-accent-contrast hover:bg-accent-hover px-3 py-2 text-[13px] font-medium cursor-pointer transition-colors"
|
||||||
@@ -59,7 +84,7 @@ const UpdateBanner: React.FC = () => {
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{downloaded && (
|
{!errored && downloaded && (
|
||||||
<button
|
<button
|
||||||
onClick={() => state.install()}
|
onClick={() => state.install()}
|
||||||
className="w-full inline-flex items-center justify-center gap-2 rounded-btn bg-accent text-accent-contrast hover:bg-accent-hover px-3 py-2 text-[13px] font-medium cursor-pointer transition-colors"
|
className="w-full inline-flex items-center justify-center gap-2 rounded-btn bg-accent text-accent-contrast hover:bg-accent-hover px-3 py-2 text-[13px] font-medium cursor-pointer transition-colors"
|
||||||
|
|||||||
@@ -77,6 +77,8 @@ const translations: Record<string, Record<string, string>> = {
|
|||||||
update_latest: '已是最新版本',
|
update_latest: '已是最新版本',
|
||||||
update_check: '检查更新',
|
update_check: '检查更新',
|
||||||
update_checking: '正在检查…',
|
update_checking: '正在检查…',
|
||||||
|
update_failed: '更新失败',
|
||||||
|
update_retry: '重试',
|
||||||
menu_more: '更多',
|
menu_more: '更多',
|
||||||
menu_theme_light: '浅色模式',
|
menu_theme_light: '浅色模式',
|
||||||
menu_theme_dark: '深色模式',
|
menu_theme_dark: '深色模式',
|
||||||
@@ -447,6 +449,8 @@ const translations: Record<string, Record<string, string>> = {
|
|||||||
update_latest: 'You are up to date',
|
update_latest: 'You are up to date',
|
||||||
update_check: 'Check for updates',
|
update_check: 'Check for updates',
|
||||||
update_checking: 'Checking…',
|
update_checking: 'Checking…',
|
||||||
|
update_failed: 'Update failed',
|
||||||
|
update_retry: 'Retry',
|
||||||
menu_more: 'More',
|
menu_more: 'More',
|
||||||
menu_theme_light: 'Light mode',
|
menu_theme_light: 'Light mode',
|
||||||
menu_theme_dark: 'Dark mode',
|
menu_theme_dark: 'Dark mode',
|
||||||
|
|||||||
@@ -50,13 +50,11 @@ export const useUpdateStore = create<UpdateState>((set, get) => ({
|
|||||||
closePanel: () => set({ panelOpen: false }),
|
closePanel: () => set({ panelOpen: false }),
|
||||||
|
|
||||||
recheck: () => {
|
recheck: () => {
|
||||||
const st = get()
|
// Open the panel and clear any dismiss so the result of this explicit check
|
||||||
// If we already know about an available/downloaded update, just re-open the
|
// is always visible — available, error, or (via the menu label) up-to-date.
|
||||||
// panel (clearing the dismiss for this version) instead of waiting on a
|
// Previously the panel only opened when an update was already known, so a
|
||||||
// network round-trip — the user asked to see it.
|
// download/check error rendered nothing and looked like a dead click.
|
||||||
if (hasAvailableUpdate(st)) {
|
set({ dismissedVersion: null, panelOpen: true })
|
||||||
set({ dismissedVersion: null, panelOpen: true })
|
|
||||||
}
|
|
||||||
// Always kick a fresh check too (picks up newer versions / recovers errors).
|
// Always kick a fresh check too (picks up newer versions / recovers errors).
|
||||||
// Pass the UI language so downloads route to the China CDN / R2 accordingly.
|
// Pass the UI language so downloads route to the China CDN / R2 accordingly.
|
||||||
window.electronAPI?.checkForUpdate?.(getLang())
|
window.electronAPI?.checkForUpdate?.(getLang())
|
||||||
|
|||||||
Reference in New Issue
Block a user