diff --git a/electron/preload.ts b/electron/preload.ts index 1cd1a89..20f6803 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -27,6 +27,7 @@ contextBridge.exposeInMainWorld('electronAPI', { }, updater: { checkForUpdates: () => ipcRenderer.invoke('updater:check'), + downloadUpdate: () => ipcRenderer.invoke('updater:download'), quitAndInstall: () => ipcRenderer.invoke('updater:quit-and-install'), onStatus: (callback: (data: unknown) => void) => { const listener = (_event: unknown, data: unknown) => callback(data); diff --git a/electron/updater.ts b/electron/updater.ts index 220b4bf..53b9120 100644 --- a/electron/updater.ts +++ b/electron/updater.ts @@ -1,6 +1,6 @@ import { autoUpdater } from 'electron-updater'; import type { BrowserWindow } from 'electron'; -import { ipcMain } from 'electron'; +import { ipcMain, session } from 'electron'; let mainWindow: BrowserWindow | null = null; @@ -8,13 +8,41 @@ function sendStatus(data: Record) { mainWindow?.webContents.send('updater:status', data); } +/** + * Resolve system proxy for GitHub and inject into electron-updater + * so that VPN / proxy tools are respected during update downloads. + */ +async function configureProxy() { + try { + const proxy = await session.defaultSession.resolveProxy('https://github.com'); + // proxy returns "DIRECT" or "PROXY host:port" / "SOCKS5 host:port" etc. + if (proxy && proxy !== 'DIRECT') { + const match = proxy.match(/^(?:PROXY|HTTPS)\s+(.+)/i); + if (match) { + process.env.HTTPS_PROXY = `http://${match[1]}`; + console.log('[updater] Using system proxy:', process.env.HTTPS_PROXY); + } + const socksMatch = proxy.match(/^SOCKS5?\s+(.+)/i); + if (socksMatch) { + process.env.HTTPS_PROXY = `socks5://${socksMatch[1]}`; + console.log('[updater] Using system SOCKS proxy:', process.env.HTTPS_PROXY); + } + } + } catch (err) { + console.warn('[updater] Failed to resolve proxy:', err); + } +} + export function initAutoUpdater(win: BrowserWindow) { mainWindow = win; - // Configuration - autoUpdater.autoDownload = true; + // Configuration — don't auto-download, let user trigger manually + autoUpdater.autoDownload = false; autoUpdater.autoInstallOnAppQuit = true; + // Resolve and apply system proxy for update downloads + configureProxy(); + // --- Events --- autoUpdater.on('checking-for-update', () => { sendStatus({ status: 'checking' }); @@ -69,6 +97,10 @@ export function initAutoUpdater(win: BrowserWindow) { return autoUpdater.checkForUpdates(); }); + ipcMain.handle('updater:download', async () => { + return autoUpdater.downloadUpdate(); + }); + ipcMain.handle('updater:quit-and-install', () => { autoUpdater.quitAndInstall(); }); diff --git a/package-lock.json b/package-lock.json index d435780..b522632 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "codepilot", - "version": "0.17.0", + "version": "0.17.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "codepilot", - "version": "0.17.0", + "version": "0.17.1", "dependencies": { "@ai-sdk/anthropic": "^3.0.47", "@ai-sdk/google": "^3.0.31", diff --git a/package.json b/package.json index d01538c..d49074b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codepilot", - "version": "0.17.0", + "version": "0.17.1", "private": true, "author": { "name": "op7418", diff --git a/src/components/layout/AppShell.tsx b/src/components/layout/AppShell.tsx index 3d4f3d6..d30c864 100644 --- a/src/components/layout/AppShell.tsx +++ b/src/components/layout/AppShell.tsx @@ -298,10 +298,11 @@ export function AppShell({ children }: { children: React.ReactNode }) { const dismissUpdate = useCallback(() => { setShowDialog(false); - if (updateInfo?.latestVersion) { - localStorage.setItem(DISMISSED_VERSION_KEY, updateInfo.latestVersion); - } - }, [updateInfo]); + }, []); + + const downloadUpdate = useCallback(() => { + window.electronAPI?.updater?.downloadUpdate(); + }, []); const quitAndInstall = useCallback(() => { window.electronAPI?.updater?.quitAndInstall(); @@ -312,12 +313,13 @@ export function AppShell({ children }: { children: React.ReactNode }) { updateInfo, checking, checkForUpdates, + downloadUpdate, dismissUpdate, showDialog, setShowDialog, quitAndInstall, }), - [updateInfo, checking, checkForUpdates, dismissUpdate, showDialog, quitAndInstall] + [updateInfo, checking, checkForUpdates, downloadUpdate, dismissUpdate, showDialog, quitAndInstall] ); const panelContextValue = useMemo( diff --git a/src/components/layout/UpdateDialog.tsx b/src/components/layout/UpdateDialog.tsx index 1bebc2c..03f5703 100644 --- a/src/components/layout/UpdateDialog.tsx +++ b/src/components/layout/UpdateDialog.tsx @@ -14,12 +14,13 @@ import { useUpdate } from "@/hooks/useUpdate"; import { useTranslation } from "@/hooks/useTranslation"; export function UpdateDialog() { - const { updateInfo, showDialog, dismissUpdate, quitAndInstall } = useUpdate(); + const { updateInfo, showDialog, dismissUpdate, downloadUpdate, quitAndInstall } = useUpdate(); const { t } = useTranslation(); if (!updateInfo?.updateAvailable) return null; const { isNativeUpdate, readyToInstall, downloadProgress } = updateInfo; + const isDownloading = isNativeUpdate && !readyToInstall && downloadProgress != null && downloadProgress > 0; return ( { @@ -68,17 +69,17 @@ export function UpdateDialog() { Current: v{updateInfo.currentVersion} → Latest: v{updateInfo.latestVersion}

- {/* Download progress bar (native update, downloading) */} - {isNativeUpdate && !readyToInstall && downloadProgress != null && downloadProgress > 0 && ( + {/* Download progress bar */} + {isDownloading && (

- {t('update.downloading')} {Math.round(downloadProgress)}% + {t('update.downloading')} {Math.round(downloadProgress!)}%

)} @@ -88,7 +89,6 @@ export function UpdateDialog() { {t('update.later')} {!isNativeUpdate ? ( - // Browser mode: open release page ) : readyToInstall ? ( - // Electron: downloaded, ready to install - ) : ( - // Electron: still downloading + ) : isDownloading ? ( + ) : ( + )} diff --git a/src/components/settings/GeneralSection.tsx b/src/components/settings/GeneralSection.tsx index 334b893..de51573 100644 --- a/src/components/settings/GeneralSection.tsx +++ b/src/components/settings/GeneralSection.tsx @@ -21,10 +21,13 @@ import { SUPPORTED_LOCALES, type Locale } from "@/i18n"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; function UpdateCard() { - const { updateInfo, checking, checkForUpdates, quitAndInstall } = useUpdate(); + const { updateInfo, checking, checkForUpdates, downloadUpdate, quitAndInstall, setShowDialog } = useUpdate(); const { t } = useTranslation(); const currentVersion = process.env.NEXT_PUBLIC_APP_VERSION || "0.0.0"; + const isDownloading = updateInfo?.isNativeUpdate && !updateInfo.readyToInstall + && updateInfo.downloadProgress != null && updateInfo.downloadProgress > 0; + return (
@@ -32,53 +35,73 @@ function UpdateCard() {

{t('settings.codepilot')}

{t('settings.version', { version: currentVersion })}

- + ) : updateInfo.isNativeUpdate && !isDownloading ? ( + + ) : !updateInfo.isNativeUpdate ? ( + + ) : null )} - {checking ? t('settings.checking') : t('settings.checkForUpdates')} - + +
{updateInfo && !checking && (
{updateInfo.updateAvailable ? ( -
- - - {updateInfo.readyToInstall - ? t('update.readyToInstall', { version: updateInfo.latestVersion }) - : updateInfo.downloadProgress != null && updateInfo.downloadProgress > 0 && !updateInfo.readyToInstall - ? `${t('update.downloading')} ${Math.round(updateInfo.downloadProgress)}%` - : t('settings.updateAvailable', { version: updateInfo.latestVersion })} - - {updateInfo.readyToInstall ? ( - - ) : !updateInfo.isNativeUpdate ? ( - - ) : null} +
+
+ + + {updateInfo.readyToInstall + ? t('update.readyToInstall', { version: updateInfo.latestVersion }) + : isDownloading + ? `${t('update.downloading')} ${Math.round(updateInfo.downloadProgress!)}%` + : t('settings.updateAvailable', { version: updateInfo.latestVersion })} + + {updateInfo.releaseNotes && ( + + )} +
+ {/* Download progress bar */} + {isDownloading && ( +
+
+
+ )}
) : (

{t('settings.latestVersion')}

diff --git a/src/hooks/useUpdate.ts b/src/hooks/useUpdate.ts index bf9cc1f..cce2841 100644 --- a/src/hooks/useUpdate.ts +++ b/src/hooks/useUpdate.ts @@ -19,6 +19,7 @@ export interface UpdateContextValue { updateInfo: UpdateInfo | null; checking: boolean; checkForUpdates: () => Promise; + downloadUpdate: () => void; dismissUpdate: () => void; showDialog: boolean; setShowDialog: (v: boolean) => void; diff --git a/src/i18n/en.ts b/src/i18n/en.ts index c0ca9e4..1e909d7 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -305,6 +305,7 @@ const en = { 'update.restartToUpdate': 'Restart to Update', 'update.restartNow': 'Restart Now', 'update.readyToInstall': 'CodePilot v{version} is ready — restart to update', + 'update.installUpdate': 'Download & Install', 'update.later': 'Later', // ── Image Generation ────────────────────────────────────── diff --git a/src/i18n/zh.ts b/src/i18n/zh.ts index 0583372..c352f55 100644 --- a/src/i18n/zh.ts +++ b/src/i18n/zh.ts @@ -302,6 +302,7 @@ const zh: Record = { 'update.restartToUpdate': '重启以更新', 'update.restartNow': '立即重启', 'update.readyToInstall': 'CodePilot v{version} 已就绪 — 重启以完成更新', + 'update.installUpdate': '下载并安装', 'update.later': '稍后', // ── Image Generation ────────────────────────────────────── diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts index ce94b02..6e95f7f 100644 --- a/src/types/electron.d.ts +++ b/src/types/electron.d.ts @@ -36,6 +36,7 @@ interface UpdateStatusEvent { interface ElectronUpdaterAPI { checkForUpdates: () => Promise; + downloadUpdate: () => Promise; quitAndInstall: () => Promise; onStatus: (callback: (data: UpdateStatusEvent) => void) => () => void; }