From 6e5cdbf748d55cb69a080e8d8a0f28e18e3a1903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Wed, 25 Feb 2026 09:40:29 +0900 Subject: [PATCH 1/4] feat(dashboard): add generic desktop app updater bridge --- .../src/i18n/locales/en-US/core/header.json | 12 ++ .../src/i18n/locales/zh-CN/core/header.json | 12 ++ .../full/vertical-header/VerticalHeader.vue | 189 +++++++++++------- dashboard/src/types/desktop-bridge.d.ts | 51 +++++ dashboard/src/types/electron-bridge.d.ts | 25 --- 5 files changed, 191 insertions(+), 98 deletions(-) create mode 100644 dashboard/src/types/desktop-bridge.d.ts delete mode 100644 dashboard/src/types/electron-bridge.d.ts diff --git a/dashboard/src/i18n/locales/en-US/core/header.json b/dashboard/src/i18n/locales/en-US/core/header.json index 080c59b132..4da98b8dd8 100644 --- a/dashboard/src/i18n/locales/en-US/core/header.json +++ b/dashboard/src/i18n/locales/en-US/core/header.json @@ -58,6 +58,18 @@ "guideStep2": "Install it and restart AstrBot.", "guideStep3": "If you use Docker, prefer the image update path." }, + "desktopApp": { + "title": "Update Desktop App", + "message": "Check and upgrade the AstrBot desktop application.", + "currentVersion": "Current version: ", + "latestVersion": "Latest version: ", + "checking": "Checking desktop app updates...", + "hasNewVersion": "A new version is available. Click confirm to upgrade.", + "isLatest": "Already on the latest version", + "installing": "Downloading and installing update. The app will restart automatically...", + "checkFailed": "Failed to check updates. Please try again later.", + "installFailed": "Upgrade failed. Please try again later." + }, "dashboardUpdate": { "title": "Update Dashboard to Latest Version Only", "currentVersion": "Current Version", diff --git a/dashboard/src/i18n/locales/zh-CN/core/header.json b/dashboard/src/i18n/locales/zh-CN/core/header.json index 8b6b3dc1e5..3bb9850331 100644 --- a/dashboard/src/i18n/locales/zh-CN/core/header.json +++ b/dashboard/src/i18n/locales/zh-CN/core/header.json @@ -58,6 +58,18 @@ "guideStep2": "完成安装后重启 AstrBot。", "guideStep3": "如果你使用 Docker,请优先使用镜像更新方式。" }, + "desktopApp": { + "title": "更新桌面应用", + "message": "将检查并升级 AstrBot 桌面端程序。", + "currentVersion": "当前版本:", + "latestVersion": "最新版本:", + "checking": "正在检查桌面应用更新...", + "hasNewVersion": "发现新版本,可点击确认升级。", + "isLatest": "已经是最新版本", + "installing": "正在下载并安装更新,完成后将自动重启应用...", + "checkFailed": "检查更新失败,请稍后重试。", + "installFailed": "升级失败,请稍后重试。" + }, "dashboardUpdate": { "title": "单独更新管理面板到最新版本", "currentVersion": "当前版本", diff --git a/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue b/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue index 543aef12cd..d8737e383c 100644 --- a/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue +++ b/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue @@ -50,24 +50,38 @@ let installLoading = ref(false); const isDesktopReleaseMode = ref( typeof window !== 'undefined' && !!window.astrbotDesktop?.isDesktop ); -const redirectConfirmDialog = ref(false); -const pendingRedirectUrl = ref(''); -const resolvingReleaseTarget = ref(false); -const DEFAULT_ASTRBOT_RELEASE_BASE_URL = 'https://github.com/AstrBotDevs/AstrBot/releases'; -const resolveReleaseBaseUrl = () => { - const raw = import.meta.env.VITE_ASTRBOT_RELEASE_BASE_URL; - // Keep upstream default on AstrBot releases; desktop distributors can override via env injection. - const normalized = raw?.trim()?.replace(/\/+$/, '') || ''; - const withoutLatestSuffix = normalized.replace(/\/latest$/i, ''); - return withoutLatestSuffix || DEFAULT_ASTRBOT_RELEASE_BASE_URL; +const desktopUpdateDialog = ref(false); +const desktopUpdateChecking = ref(false); +const desktopUpdateInstalling = ref(false); +const desktopUpdateHasNewVersion = ref(false); +const desktopUpdateCurrentVersion = ref('-'); +const desktopUpdateLatestVersion = ref('-'); +const desktopUpdateStatus = ref(''); + +type AppUpdaterBridge = { + checkForAppUpdate: () => Promise<{ + ok: boolean; + reason: string | null; + currentVersion: string; + latestVersion: string | null; + hasUpdate: boolean; + }>; + installAppUpdate: () => Promise<{ + ok: boolean; + reason: string | null; + }>; }; -const releaseBaseUrl = resolveReleaseBaseUrl(); -const getReleaseUrlByTag = (tag: string | null | undefined) => { - const normalizedTag = (tag || '').trim(); - if (!normalizedTag || normalizedTag.toLowerCase() === 'latest') { - return `${releaseBaseUrl}/latest`; + +const getAppUpdaterBridge = (): AppUpdaterBridge | null => { + const bridge = window.astrbotAppUpdater; + if ( + bridge && + typeof bridge.checkForAppUpdate === 'function' && + typeof bridge.installAppUpdate === 'function' + ) { + return bridge; } - return `${releaseBaseUrl}/tag/${normalizedTag}`; + return null; }; const getSelectedGitHubProxy = () => { @@ -89,16 +103,6 @@ const releasesHeader = computed(() => [ { title: t('core.header.updateDialog.table.sourceUrl'), key: 'zipball_url' }, { title: t('core.header.updateDialog.table.actions'), key: 'switch' } ]); -const latestReleaseTag = computed(() => { - const firstRelease = (releases.value as any[])?.[0]; - if (firstRelease?.tag_name) { - return firstRelease.tag_name as string; - } - return hasNewVersion.value - ? t('core.header.updateDialog.redirectConfirm.latestLabel') - : (botCurrVersion.value || '-'); -}); - // Form validation const formValid = ref(true); const passwordRules = computed(() => [ @@ -126,47 +130,88 @@ const accountEditStatus = ref({ message: '' }); -const open = (link: string) => { - window.open(link, '_blank'); -}; +function cancelDesktopUpdate() { + if (desktopUpdateInstalling.value) { + return; + } + desktopUpdateDialog.value = false; +} + +async function openDesktopUpdateDialog() { + desktopUpdateDialog.value = true; + desktopUpdateChecking.value = true; + desktopUpdateInstalling.value = false; + desktopUpdateHasNewVersion.value = false; + desktopUpdateCurrentVersion.value = '-'; + desktopUpdateLatestVersion.value = '-'; + desktopUpdateStatus.value = t('core.header.updateDialog.desktopApp.checking'); + + const bridge = getAppUpdaterBridge(); + if (!bridge) { + desktopUpdateChecking.value = false; + desktopUpdateStatus.value = t('core.header.updateDialog.desktopApp.checkFailed'); + return; + } -function requestExternalRedirect(link: string) { - pendingRedirectUrl.value = link; - redirectConfirmDialog.value = true; -} + try { + const result = await bridge.checkForAppUpdate(); + if (!result?.ok) { + desktopUpdateCurrentVersion.value = result?.currentVersion || '-'; + desktopUpdateLatestVersion.value = + result?.latestVersion || result?.currentVersion || '-'; + desktopUpdateStatus.value = + result?.reason || t('core.header.updateDialog.desktopApp.checkFailed'); + return; + } -function cancelExternalRedirect() { - redirectConfirmDialog.value = false; - pendingRedirectUrl.value = ''; + desktopUpdateCurrentVersion.value = result.currentVersion || '-'; + desktopUpdateLatestVersion.value = + result.latestVersion || result.currentVersion || '-'; + desktopUpdateHasNewVersion.value = !!result.hasUpdate; + desktopUpdateStatus.value = result.hasUpdate + ? t('core.header.updateDialog.desktopApp.hasNewVersion') + : t('core.header.updateDialog.desktopApp.isLatest'); + } catch (error) { + console.log(error); + desktopUpdateStatus.value = t('core.header.updateDialog.desktopApp.checkFailed'); + } finally { + desktopUpdateChecking.value = false; + } } -function confirmExternalRedirect() { - const targetUrl = pendingRedirectUrl.value; - cancelExternalRedirect(); - if (targetUrl) { - open(targetUrl); +async function confirmDesktopUpdate() { + if (!desktopUpdateHasNewVersion.value || desktopUpdateInstalling.value) { + return; } -} -const getReleaseUrlForDesktop = () => { - const firstRelease = (releases.value as any[])?.[0]; - if (firstRelease?.tag_name) { - return getReleaseUrlByTag(firstRelease.tag_name as string); + const bridge = getAppUpdaterBridge(); + if (!bridge) { + desktopUpdateStatus.value = t('core.header.updateDialog.desktopApp.installFailed'); + return; } - if (hasNewVersion.value) return getReleaseUrlByTag('latest'); - const tag = botCurrVersion.value?.startsWith('v') ? botCurrVersion.value : 'latest'; - return getReleaseUrlByTag(tag); -}; + + desktopUpdateInstalling.value = true; + desktopUpdateStatus.value = t('core.header.updateDialog.desktopApp.installing'); + + try { + const result = await bridge.installAppUpdate(); + if (result?.ok) { + desktopUpdateStatus.value = t('core.header.updateDialog.desktopApp.installing'); + return; + } + desktopUpdateStatus.value = + result?.reason || t('core.header.updateDialog.desktopApp.installFailed'); + } catch (error) { + console.log(error); + desktopUpdateStatus.value = t('core.header.updateDialog.desktopApp.installFailed'); + } finally { + desktopUpdateInstalling.value = false; + } +} function handleUpdateClick() { if (isDesktopReleaseMode.value) { - requestExternalRedirect(''); - resolvingReleaseTarget.value = true; - checkUpdate(); - void getReleases().finally(() => { - pendingRedirectUrl.value = getReleaseUrlForDesktop() || getReleaseUrlByTag('latest'); - resolvingReleaseTarget.value = false; - }); + void openDesktopUpdateDialog(); return; } checkUpdate(); @@ -680,40 +725,38 @@ onMounted(async () => { - + - {{ t('core.header.updateDialog.redirectConfirm.title') }} + {{ t('core.header.updateDialog.desktopApp.title') }}
- {{ t('core.header.updateDialog.redirectConfirm.message') }} + {{ t('core.header.updateDialog.desktopApp.message') }}
- {{ t('core.header.updateDialog.redirectConfirm.targetVersion') }} - {{ latestReleaseTag }} - + {{ t('core.header.updateDialog.desktopApp.currentVersion') }} + {{ desktopUpdateCurrentVersion }}
-
- {{ t('core.header.updateDialog.redirectConfirm.currentVersion') }} - {{ botCurrVersion || '-' }} +
+ {{ t('core.header.updateDialog.desktopApp.latestVersion') }} + {{ desktopUpdateLatestVersion }} +
-
{{ t('core.header.updateDialog.redirectConfirm.guideTitle') }}
-
1. {{ t('core.header.updateDialog.redirectConfirm.guideStep1') }}
-
2. {{ t('core.header.updateDialog.redirectConfirm.guideStep2') }}
-
3. {{ t('core.header.updateDialog.redirectConfirm.guideStep3') }}
+ {{ desktopUpdateStatus }}
- + {{ t('core.common.dialog.cancelButton') }} - + {{ t('core.common.dialog.confirmButton') }} diff --git a/dashboard/src/types/desktop-bridge.d.ts b/dashboard/src/types/desktop-bridge.d.ts new file mode 100644 index 0000000000..67ff888506 --- /dev/null +++ b/dashboard/src/types/desktop-bridge.d.ts @@ -0,0 +1,51 @@ +export {}; + +declare global { + interface AstrBotAppUpdaterBridge { + checkForAppUpdate: () => Promise<{ + ok: boolean; + reason: string | null; + currentVersion: string; + latestVersion: string | null; + hasUpdate: boolean; + }>; + installAppUpdate: () => Promise<{ + ok: boolean; + reason: string | null; + }>; + } + + interface Window { + astrbotAppUpdater?: AstrBotAppUpdaterBridge; + astrbotDesktop?: { + isDesktop: boolean; + isDesktopRuntime: () => Promise; + getBackendState: () => Promise<{ + running: boolean; + spawning: boolean; + restarting: boolean; + canManage: boolean; + }>; + restartBackend: (authToken?: string | null) => Promise<{ + ok: boolean; + reason: string | null; + }>; + stopBackend: () => Promise<{ + ok: boolean; + reason: string | null; + }>; + checkDesktopAppUpdate: () => Promise<{ + ok: boolean; + reason: string | null; + currentVersion: string; + latestVersion: string | null; + hasUpdate: boolean; + }>; + installDesktopAppUpdate: () => Promise<{ + ok: boolean; + reason: string | null; + }>; + onTrayRestartBackend?: (callback: () => void) => () => void; + }; + } +} diff --git a/dashboard/src/types/electron-bridge.d.ts b/dashboard/src/types/electron-bridge.d.ts deleted file mode 100644 index b42ffc0cf1..0000000000 --- a/dashboard/src/types/electron-bridge.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -export {}; - -declare global { - interface Window { - astrbotDesktop?: { - isDesktop: boolean; - isDesktopRuntime: () => Promise; - getBackendState: () => Promise<{ - running: boolean; - spawning: boolean; - restarting: boolean; - canManage: boolean; - }>; - restartBackend: (authToken?: string | null) => Promise<{ - ok: boolean; - reason: string | null; - }>; - stopBackend: () => Promise<{ - ok: boolean; - reason: string | null; - }>; - onTrayRestartBackend?: (callback: () => void) => () => void; - }; - } -} From 55bbd098ca2fd06a7dc4b908ab2e7d5ed38f1307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Wed, 25 Feb 2026 09:49:02 +0900 Subject: [PATCH 2/4] fix(dashboard): address updater bridge review feedback --- .../full/vertical-header/VerticalHeader.vue | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue b/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue index d8737e383c..985455e445 100644 --- a/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue +++ b/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue @@ -58,21 +58,12 @@ const desktopUpdateCurrentVersion = ref('-'); const desktopUpdateLatestVersion = ref('-'); const desktopUpdateStatus = ref(''); -type AppUpdaterBridge = { - checkForAppUpdate: () => Promise<{ - ok: boolean; - reason: string | null; - currentVersion: string; - latestVersion: string | null; - hasUpdate: boolean; - }>; - installAppUpdate: () => Promise<{ - ok: boolean; - reason: string | null; - }>; -}; +type AppUpdaterBridge = NonNullable; const getAppUpdaterBridge = (): AppUpdaterBridge | null => { + if (typeof window === 'undefined') { + return null; + } const bridge = window.astrbotAppUpdater; if ( bridge && @@ -196,7 +187,7 @@ async function confirmDesktopUpdate() { try { const result = await bridge.installAppUpdate(); if (result?.ok) { - desktopUpdateStatus.value = t('core.header.updateDialog.desktopApp.installing'); + desktopUpdateDialog.value = false; return; } desktopUpdateStatus.value = From 1f74252e5af15987854d929b96225539ef5e6d25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Wed, 25 Feb 2026 09:53:30 +0900 Subject: [PATCH 3/4] fix(dashboard): unify updater bridge types and error logging --- .../full/vertical-header/VerticalHeader.vue | 4 +- dashboard/src/types/desktop-bridge.d.ts | 39 ++++++++----------- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue b/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue index 985455e445..4939ab5884 100644 --- a/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue +++ b/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue @@ -163,7 +163,7 @@ async function openDesktopUpdateDialog() { ? t('core.header.updateDialog.desktopApp.hasNewVersion') : t('core.header.updateDialog.desktopApp.isLatest'); } catch (error) { - console.log(error); + console.error(error); desktopUpdateStatus.value = t('core.header.updateDialog.desktopApp.checkFailed'); } finally { desktopUpdateChecking.value = false; @@ -193,7 +193,7 @@ async function confirmDesktopUpdate() { desktopUpdateStatus.value = result?.reason || t('core.header.updateDialog.desktopApp.installFailed'); } catch (error) { - console.log(error); + console.error(error); desktopUpdateStatus.value = t('core.header.updateDialog.desktopApp.installFailed'); } finally { desktopUpdateInstalling.value = false; diff --git a/dashboard/src/types/desktop-bridge.d.ts b/dashboard/src/types/desktop-bridge.d.ts index 67ff888506..fd91094d59 100644 --- a/dashboard/src/types/desktop-bridge.d.ts +++ b/dashboard/src/types/desktop-bridge.d.ts @@ -1,18 +1,22 @@ export {}; declare global { + interface AstrBotDesktopAppUpdateCheckResult { + ok: boolean; + reason: string | null; + currentVersion: string; + latestVersion: string | null; + hasUpdate: boolean; + } + + interface AstrBotDesktopAppUpdateResult { + ok: boolean; + reason: string | null; + } + interface AstrBotAppUpdaterBridge { - checkForAppUpdate: () => Promise<{ - ok: boolean; - reason: string | null; - currentVersion: string; - latestVersion: string | null; - hasUpdate: boolean; - }>; - installAppUpdate: () => Promise<{ - ok: boolean; - reason: string | null; - }>; + checkForAppUpdate: () => Promise; + installAppUpdate: () => Promise; } interface Window { @@ -34,17 +38,8 @@ declare global { ok: boolean; reason: string | null; }>; - checkDesktopAppUpdate: () => Promise<{ - ok: boolean; - reason: string | null; - currentVersion: string; - latestVersion: string | null; - hasUpdate: boolean; - }>; - installDesktopAppUpdate: () => Promise<{ - ok: boolean; - reason: string | null; - }>; + checkDesktopAppUpdate: () => Promise; + installDesktopAppUpdate: () => Promise; onTrayRestartBackend?: (callback: () => void) => () => void; }; } From 3f56e6b5857d69c25f9f60ed695b99faac3c8a82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Wed, 25 Feb 2026 09:58:12 +0900 Subject: [PATCH 4/4] fix(dashboard): consolidate updater bridge typings --- .../layouts/full/vertical-header/VerticalHeader.vue | 4 +--- dashboard/src/types/desktop-bridge.d.ts | 10 ++++------ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue b/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue index 4939ab5884..47905d4ff8 100644 --- a/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue +++ b/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue @@ -58,9 +58,7 @@ const desktopUpdateCurrentVersion = ref('-'); const desktopUpdateLatestVersion = ref('-'); const desktopUpdateStatus = ref(''); -type AppUpdaterBridge = NonNullable; - -const getAppUpdaterBridge = (): AppUpdaterBridge | null => { +const getAppUpdaterBridge = (): AstrBotAppUpdaterBridge | null => { if (typeof window === 'undefined') { return null; } diff --git a/dashboard/src/types/desktop-bridge.d.ts b/dashboard/src/types/desktop-bridge.d.ts index fd91094d59..a51500b150 100644 --- a/dashboard/src/types/desktop-bridge.d.ts +++ b/dashboard/src/types/desktop-bridge.d.ts @@ -3,15 +3,15 @@ export {}; declare global { interface AstrBotDesktopAppUpdateCheckResult { ok: boolean; - reason: string | null; - currentVersion: string; - latestVersion: string | null; + reason?: string | null; + currentVersion?: string; + latestVersion?: string | null; hasUpdate: boolean; } interface AstrBotDesktopAppUpdateResult { ok: boolean; - reason: string | null; + reason?: string | null; } interface AstrBotAppUpdaterBridge { @@ -38,8 +38,6 @@ declare global { ok: boolean; reason: string | null; }>; - checkDesktopAppUpdate: () => Promise; - installDesktopAppUpdate: () => Promise; onTrayRestartBackend?: (callback: () => void) => () => void; }; }