@@ -134,6 +134,16 @@ const desktopUpdateCurrentVersion = ref("-");
134134const desktopUpdateLatestVersion = ref (" -" );
135135const desktopUpdateStatus = ref (" " );
136136const desktopDownloadProgress = ref <AstrBotDesktopAppUpdateProgress | null >(null );
137+ const appHasNewVersion = computed (() =>
138+ isDesktopReleaseMode .value
139+ ? desktopUpdateHasNewVersion .value
140+ : hasNewVersion .value ,
141+ );
142+ const hasAvailableUpdate = computed (
143+ () =>
144+ appHasNewVersion .value ||
145+ (! isDesktopReleaseMode .value && dashboardHasNewVersion .value ),
146+ );
137147const isChatPath = computed (
138148 () => route .path === " /chat" || route .path .startsWith (" /chat/" ),
139149);
@@ -297,10 +307,11 @@ function cancelDesktopUpdate() {
297307 desktopUpdateDialog .value = false ;
298308}
299309
300- async function openDesktopUpdateDialog( ) {
310+ async function checkDesktopUpdate( showDialog = false ) {
301311 if (desktopUpdateInstalling .value ) return ;
312+ if (showDialog ) desktopUpdateDialog .value = true ;
313+ if (desktopUpdateChecking .value ) return ;
302314 desktopDownloadProgress .value = null ;
303- desktopUpdateDialog .value = true ;
304315 desktopUpdateChecking .value = true ;
305316 desktopUpdateInstalling .value = false ;
306317 desktopUpdateHasNewVersion .value = false ;
@@ -310,16 +321,29 @@ async function openDesktopUpdateDialog() {
310321
311322 const bridge = getAppUpdaterBridge ();
312323 if (! bridge ) {
324+ console .warn (" [desktop-update] App updater bridge is unavailable." );
313325 desktopUpdateChecking .value = false ;
314326 desktopUpdateStatus .value = t (
315327 " core.header.updateDialog.desktopApp.checkFailed" ,
316328 );
317329 return ;
318330 }
319331
332+ let checkTimeout: ReturnType <typeof setTimeout > | undefined ;
320333 try {
321- const result = await bridge .checkForAppUpdate ();
334+ const result = await Promise .race ([
335+ bridge .checkForAppUpdate (),
336+ new Promise <never >((_ , reject ) => {
337+ checkTimeout = setTimeout (() => {
338+ reject (new Error (" Desktop app update check timed out." ));
339+ }, 30_000 );
340+ }),
341+ ]);
322342 if (! result ?.ok ) {
343+ console .warn (
344+ " [desktop-update] Failed to check for app updates." ,
345+ result ?.reason || " Unknown error" ,
346+ );
323347 desktopUpdateCurrentVersion .value = result ?.currentVersion || " -" ;
324348 desktopUpdateLatestVersion .value =
325349 result ?.latestVersion || result ?.currentVersion || " -" ;
@@ -336,11 +360,12 @@ async function openDesktopUpdateDialog() {
336360 ? t (" core.header.updateDialog.desktopApp.hasNewVersion" )
337361 : t (" core.header.updateDialog.desktopApp.isLatest" );
338362 } catch (error ) {
339- console .error (error );
363+ console .error (" [desktop-update] Failed to check for app updates. " , error );
340364 desktopUpdateStatus .value = t (
341365 " core.header.updateDialog.desktopApp.checkFailed" ,
342366 );
343367 } finally {
368+ if (checkTimeout !== undefined ) clearTimeout (checkTimeout );
344369 desktopUpdateChecking .value = false ;
345370 }
346371}
@@ -391,7 +416,7 @@ async function confirmDesktopUpdate() {
391416
392417function handleUpdateClick() {
393418 if (isDesktopReleaseMode .value ) {
394- void openDesktopUpdateDialog ( );
419+ void checkDesktopUpdate ( true );
395420 return ;
396421 }
397422 checkUpdate ();
@@ -1043,6 +1068,7 @@ onMounted(async () => {
10431068 isDesktopReleaseMode .value = runtimeInfo .isDesktopRuntime ;
10441069 if (isDesktopReleaseMode .value ) {
10451070 dashboardHasNewVersion .value = false ;
1071+ void checkDesktopUpdate ();
10461072 }
10471073
10481074 // The toolbar band doubles as the window drag region on desktop (no native title bar).
@@ -1089,15 +1115,20 @@ onMounted(async () => {
10891115
10901116 <v-spacer />
10911117
1092- <!-- 版本提示信息 - 在手机上隐藏 -->
1093- <div v-if =" !isChatPath" class =" mr-4 hidden-xs" >
1094- <small v-if =" hasNewVersion" >
1118+ <!-- Desktop keeps the update notice visible in both Bot and Chat modes. -->
1119+ <button
1120+ v-if =" hasAvailableUpdate && (!isChatPath || isDesktopReleaseMode)"
1121+ type =" button"
1122+ class =" update-notice mr-4 hidden-xs text-no-wrap"
1123+ @click =" handleUpdateClick"
1124+ >
1125+ <small v-if =" appHasNewVersion" >
10951126 {{ t("core.header.version.hasNewVersion") }}
10961127 </small >
10971128 <small v-else-if =" dashboardHasNewVersion && !isDesktopReleaseMode" >
10981129 {{ t("core.header.version.dashboardHasNewVersion") }}
10991130 </small >
1100- </div >
1131+ </button >
11011132
11021133 <div class =" header-actions" :class =" { 'chat-header-actions': isChatPath }" >
11031134 <v-btn
@@ -1155,7 +1186,9 @@ onMounted(async () => {
11551186 icon
11561187 :ripple =" false"
11571188 >
1158- <v-icon >mdi-dots-vertical</v-icon >
1189+ <v-badge :model-value =" hasAvailableUpdate" color =" primary" dot floating >
1190+ <v-icon >mdi-dots-vertical</v-icon >
1191+ </v-badge >
11591192 <span class =" header-toolbar-label" >{{ t("core.navigation.options") }}</span >
11601193 </v-btn >
11611194 </template >
@@ -1324,9 +1357,7 @@ onMounted(async () => {
13241357 }}</v-list-item-title >
13251358 <template
13261359 v-slot :append
1327- v-if ="
1328- hasNewVersion || (dashboardHasNewVersion && ! isDesktopReleaseMode )
1329- "
1360+ v-if =" hasAvailableUpdate "
13301361 >
13311362 <v-chip size =" x-small" color =" primary" variant =" tonal" class =" ml-2"
13321363 >!</v-chip
@@ -2499,6 +2530,14 @@ onMounted(async () => {
24992530 user-select : none ;
25002531}
25012532
2533+ .top-header .update-notice {
2534+ cursor : pointer ;
2535+ }
2536+
2537+ .top-header .update-notice :hover {
2538+ text-decoration : underline ;
2539+ }
2540+
25022541/* Mount point for page-contextual toolbar content (see the template note). */
25032542.app-header-context {
25042543 display : flex ;
0 commit comments