[codex] fix desktop tray icon and update checks (#1201)
* fix desktop tray icon and update errors * fix brotlicffi error compatibility * fix windows installer app shutdown * fix desktop updater artifact names
This commit is contained in:
@@ -24,6 +24,7 @@ type TranslationKey =
|
||||
| 'update.later'
|
||||
| 'update.failedTitle'
|
||||
| 'update.failedMessage'
|
||||
| 'update.noUpdateInfoMessage'
|
||||
| 'update.packagedOnlyMessage'
|
||||
| 'common.ok'
|
||||
|
||||
@@ -52,6 +53,7 @@ const translations: Record<DesktopLocale, Record<TranslationKey, string>> = {
|
||||
'update.later': 'Later',
|
||||
'update.failedTitle': 'Update check failed',
|
||||
'update.failedMessage': 'Could not check for Hermes Studio updates.',
|
||||
'update.noUpdateInfoMessage': 'Update information is not available for this platform yet.',
|
||||
'update.packagedOnlyMessage': 'Automatic updates are only available in the packaged desktop app.',
|
||||
'common.ok': 'OK',
|
||||
},
|
||||
@@ -77,6 +79,7 @@ const translations: Record<DesktopLocale, Record<TranslationKey, string>> = {
|
||||
'update.later': '稍后',
|
||||
'update.failedTitle': '检查更新失败',
|
||||
'update.failedMessage': '无法检查 Hermes Studio 更新。',
|
||||
'update.noUpdateInfoMessage': '当前平台的更新信息暂不可用。',
|
||||
'update.packagedOnlyMessage': '自动更新仅在打包后的桌面应用中可用。',
|
||||
'common.ok': '确定',
|
||||
},
|
||||
@@ -102,6 +105,7 @@ const translations: Record<DesktopLocale, Record<TranslationKey, string>> = {
|
||||
'update.later': '稍後',
|
||||
'update.failedTitle': '檢查更新失敗',
|
||||
'update.failedMessage': '無法檢查 Hermes Studio 更新。',
|
||||
'update.noUpdateInfoMessage': '目前平台的更新資訊暫不可用。',
|
||||
'update.packagedOnlyMessage': '自動更新僅可在打包後的桌面應用中使用。',
|
||||
'common.ok': '確定',
|
||||
},
|
||||
@@ -127,6 +131,7 @@ const translations: Record<DesktopLocale, Record<TranslationKey, string>> = {
|
||||
'update.later': '後で',
|
||||
'update.failedTitle': 'アップデート確認に失敗しました',
|
||||
'update.failedMessage': 'Hermes Studio のアップデートを確認できませんでした。',
|
||||
'update.noUpdateInfoMessage': 'このプラットフォームのアップデート情報はまだ利用できません。',
|
||||
'update.packagedOnlyMessage': '自動アップデートはパッケージ版デスクトップアプリでのみ利用できます。',
|
||||
'common.ok': 'OK',
|
||||
},
|
||||
@@ -152,6 +157,7 @@ const translations: Record<DesktopLocale, Record<TranslationKey, string>> = {
|
||||
'update.later': '나중에',
|
||||
'update.failedTitle': '업데이트 확인 실패',
|
||||
'update.failedMessage': 'Hermes Studio 업데이트를 확인할 수 없습니다.',
|
||||
'update.noUpdateInfoMessage': '이 플랫폼의 업데이트 정보를 아직 사용할 수 없습니다.',
|
||||
'update.packagedOnlyMessage': '자동 업데이트는 패키징된 데스크톱 앱에서만 사용할 수 있습니다.',
|
||||
'common.ok': '확인',
|
||||
},
|
||||
@@ -177,6 +183,7 @@ const translations: Record<DesktopLocale, Record<TranslationKey, string>> = {
|
||||
'update.later': 'Plus tard',
|
||||
'update.failedTitle': 'Echec de la recherche de mise a jour',
|
||||
'update.failedMessage': 'Impossible de rechercher les mises a jour de Hermes Studio.',
|
||||
'update.noUpdateInfoMessage': 'Les informations de mise a jour ne sont pas encore disponibles pour cette plateforme.',
|
||||
'update.packagedOnlyMessage': 'Les mises a jour automatiques ne sont disponibles que dans l application de bureau packagee.',
|
||||
'common.ok': 'OK',
|
||||
},
|
||||
@@ -202,6 +209,7 @@ const translations: Record<DesktopLocale, Record<TranslationKey, string>> = {
|
||||
'update.later': 'Mas tarde',
|
||||
'update.failedTitle': 'Error al buscar actualizaciones',
|
||||
'update.failedMessage': 'No se pudieron buscar actualizaciones de Hermes Studio.',
|
||||
'update.noUpdateInfoMessage': 'La informacion de actualizacion aun no esta disponible para esta plataforma.',
|
||||
'update.packagedOnlyMessage': 'Las actualizaciones automaticas solo estan disponibles en la app de escritorio empaquetada.',
|
||||
'common.ok': 'Aceptar',
|
||||
},
|
||||
@@ -227,6 +235,7 @@ const translations: Record<DesktopLocale, Record<TranslationKey, string>> = {
|
||||
'update.later': 'Spater',
|
||||
'update.failedTitle': 'Update-Prufung fehlgeschlagen',
|
||||
'update.failedMessage': 'Updates fur Hermes Studio konnten nicht gepruft werden.',
|
||||
'update.noUpdateInfoMessage': 'Update-Informationen sind fur diese Plattform noch nicht verfugbar.',
|
||||
'update.packagedOnlyMessage': 'Automatische Updates sind nur in der paketierten Desktop-App verfugbar.',
|
||||
'common.ok': 'OK',
|
||||
},
|
||||
@@ -252,6 +261,7 @@ const translations: Record<DesktopLocale, Record<TranslationKey, string>> = {
|
||||
'update.later': 'Depois',
|
||||
'update.failedTitle': 'Falha ao verificar atualizacoes',
|
||||
'update.failedMessage': 'Nao foi possivel verificar atualizacoes do Hermes Studio.',
|
||||
'update.noUpdateInfoMessage': 'As informacoes de atualizacao ainda nao estao disponiveis para esta plataforma.',
|
||||
'update.packagedOnlyMessage': 'Atualizacoes automaticas estao disponiveis apenas no app desktop empacotado.',
|
||||
'common.ok': 'OK',
|
||||
},
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { app, BrowserWindow, Menu, Tray, shell, ipcMain, nativeImage } from 'electron'
|
||||
import { join } from 'node:path'
|
||||
import { startWebUiServer, stopWebUiServer, getToken } from './webui-server'
|
||||
import { desktopIcon, desktopTrayTemplateIcon, hermesBinExists, hermesBin } from './paths'
|
||||
import { desktopIcon, desktopTrayTemplateIcon, desktopWindowsTrayIcon, hermesBinExists, hermesBin } from './paths'
|
||||
import { checkForDesktopUpdates, initAutoUpdater } from './updater'
|
||||
import { t } from './desktop-i18n'
|
||||
|
||||
const PORT = Number(process.env.HERMES_DESKTOP_PORT) || 8748
|
||||
const START_HIDDEN = process.argv.includes('--hidden')
|
||||
const QUIT_EXISTING = process.argv.includes('--quit')
|
||||
|
||||
let mainWindow: BrowserWindow | null = null
|
||||
let serverUrl: string | null = null
|
||||
@@ -90,10 +91,14 @@ function updateTrayMenu() {
|
||||
|
||||
function createTray() {
|
||||
if (tray) return
|
||||
const source = process.platform === 'darwin' ? desktopTrayTemplateIcon() : desktopIcon()
|
||||
const source = process.platform === 'darwin'
|
||||
? desktopTrayTemplateIcon()
|
||||
: process.platform === 'win32'
|
||||
? desktopWindowsTrayIcon()
|
||||
: desktopIcon()
|
||||
const icon = nativeImage.createFromPath(source).resize({
|
||||
width: process.platform === 'darwin' ? 18 : 16,
|
||||
height: process.platform === 'darwin' ? 18 : 16,
|
||||
width: process.platform === 'darwin' ? 18 : process.platform === 'win32' ? 20 : 16,
|
||||
height: process.platform === 'darwin' ? 18 : process.platform === 'win32' ? 20 : 16,
|
||||
quality: 'best',
|
||||
})
|
||||
if (process.platform === 'darwin') {
|
||||
@@ -205,11 +210,20 @@ const gotLock = app.requestSingleInstanceLock()
|
||||
if (!gotLock) {
|
||||
app.quit()
|
||||
} else {
|
||||
app.on('second-instance', () => {
|
||||
app.on('second-instance', (_event, argv) => {
|
||||
if (argv.includes('--quit')) {
|
||||
quitApp()
|
||||
return
|
||||
}
|
||||
showMainWindow()
|
||||
})
|
||||
|
||||
app.whenReady().then(() => {
|
||||
if (QUIT_EXISTING) {
|
||||
quitApp()
|
||||
return
|
||||
}
|
||||
|
||||
// Drop the default File/Edit/View/Window menu on Windows/Linux. The web
|
||||
// UI provides its own in-page controls, so the native menu bar is just
|
||||
// visual clutter. macOS keeps a menu (system requirement) but Electron's
|
||||
|
||||
@@ -45,6 +45,11 @@ export function desktopIcon(): string {
|
||||
return resolve(app.getAppPath(), 'build', 'icon.png')
|
||||
}
|
||||
|
||||
export function desktopWindowsTrayIcon(): string {
|
||||
if (app.isPackaged) return resolve(process.resourcesPath, 'build', 'icon.ico')
|
||||
return resolve(app.getAppPath(), 'build', 'icon.ico')
|
||||
}
|
||||
|
||||
export function desktopTrayTemplateIcon(): string {
|
||||
if (app.isPackaged) return resolve(process.resourcesPath, 'build', 'trayTemplate.png')
|
||||
return resolve(app.getAppPath(), 'build', 'trayTemplate.png')
|
||||
|
||||
@@ -13,6 +13,13 @@ interface GitHubRelease {
|
||||
tag_name?: string
|
||||
}
|
||||
|
||||
class MissingUpdateInfoError extends Error {
|
||||
constructor(public readonly url: string) {
|
||||
super(`Update information is not available at ${url}`)
|
||||
this.name = 'MissingUpdateInfoError'
|
||||
}
|
||||
}
|
||||
|
||||
interface AutoUpdaterOptions {
|
||||
beforeQuitAndInstall?: () => void
|
||||
}
|
||||
@@ -31,14 +38,34 @@ async function getLatestReleaseTag(): Promise<string> {
|
||||
const release = await res.json() as GitHubRelease
|
||||
const tag = release.tag_name?.trim()
|
||||
if (!tag) throw new Error('Latest release response did not include a tag')
|
||||
return tag.startsWith('v') ? tag : `v${tag}`
|
||||
return tag
|
||||
}
|
||||
|
||||
function updateManifestFile(): string {
|
||||
if (process.platform === 'darwin') return 'latest-mac.yml'
|
||||
if (process.platform === 'win32') return 'latest.yml'
|
||||
return 'latest-linux.yml'
|
||||
}
|
||||
|
||||
async function assertUpdateManifestExists(feedUrl: string): Promise<void> {
|
||||
const manifestUrl = `${feedUrl}/${updateManifestFile()}`
|
||||
const res = await fetch(manifestUrl, {
|
||||
method: 'HEAD',
|
||||
headers: {
|
||||
'User-Agent': `Hermes-Studio/${app.getVersion()}`,
|
||||
},
|
||||
})
|
||||
if (res.status === 404) throw new MissingUpdateInfoError(manifestUrl)
|
||||
if (!res.ok) throw new Error(`Update feed returned ${res.status}`)
|
||||
}
|
||||
|
||||
async function configureFeedFromLatestRelease(): Promise<void> {
|
||||
const tag = await getLatestReleaseTag()
|
||||
const feedUrl = `${CLOUDFLARE_DOWNLOAD_BASE_URL}/${tag}`
|
||||
await assertUpdateManifestExists(feedUrl)
|
||||
autoUpdater.setFeedURL({
|
||||
provider: 'generic',
|
||||
url: `${CLOUDFLARE_DOWNLOAD_BASE_URL}/${tag}`,
|
||||
url: feedUrl,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -54,12 +81,11 @@ function showUpToDate(info?: UpdateInfo) {
|
||||
}
|
||||
|
||||
function showUpdateCheckFailed(err: unknown) {
|
||||
const detail = err instanceof Error ? err.message : String(err)
|
||||
const isMissingUpdateInfo = err instanceof MissingUpdateInfoError
|
||||
dialog.showMessageBox({
|
||||
type: 'error',
|
||||
title: t('update.failedTitle'),
|
||||
message: t('update.failedMessage'),
|
||||
detail,
|
||||
type: isMissingUpdateInfo ? 'info' : 'error',
|
||||
title: isMissingUpdateInfo ? t('update.upToDateTitle') : t('update.failedTitle'),
|
||||
message: isMissingUpdateInfo ? t('update.noUpdateInfoMessage') : t('update.failedMessage'),
|
||||
buttons: [t('common.ok')],
|
||||
}).catch(() => undefined)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user