@@ -515,14 +515,52 @@ export const resolveDownloadMethods = {
515515 return this . downloadProgressByMissingKey ;
516516 } ,
517517
518- getDownloadStateKey ( missing ) {
518+ getDownloadWorkflowScopeIdentity ( context = { } ) {
519+ const workflowKey = String ( context . workflowKey || context . workflow_key || '' ) . trim ( ) ;
520+ return String (
521+ context . workflowTabId
522+ || context . workflow_tab_id
523+ || context . workflowId
524+ || context . workflow_id
525+ || context . workflowRouteKey
526+ || context . workflow_route_key
527+ || workflowKey . split ( '\n' ) [ 0 ]
528+ || ''
529+ ) . trim ( ) ;
530+ } ,
531+
532+ getCurrentDownloadWorkflowScopeIdentity ( ) {
533+ const context = this . getActiveWorkflowTabContext ?. ( ) || { } ;
534+ return this . getDownloadWorkflowScopeIdentity ( {
535+ ...context ,
536+ workflowKey : this . getWorkflowScopedQueueKey ?. ( ) || '' ,
537+ workflowRouteKey : context . workflowRouteKey || this . getActiveWorkflowRouteKey ?. ( ) || this . activeWorkflowRouteKey || '' ,
538+ workflowId : context . workflowId || this . getActiveWorkflowId ?. ( ) || ''
539+ } ) ;
540+ } ,
541+
542+ isDownloadInCurrentWorkflowScope ( info = { } ) {
543+ const downloadScope = this . getDownloadWorkflowScopeIdentity ( info ) ;
544+ const currentScope = this . getCurrentDownloadWorkflowScopeIdentity ( ) ;
545+ return ! downloadScope || ! currentScope || downloadScope === currentScope ;
546+ } ,
547+
548+ getDownloadMissingIdentity ( missing ) {
519549 return this . getMissingSearchKey ?. ( missing ) || this . getMissingModelKey ( missing ) ;
520550 } ,
521551
552+ getDownloadStateKey ( missing , context = null ) {
553+ const missingKey = this . getDownloadMissingIdentity ( missing ) ;
554+ const workflowScope = context
555+ ? this . getDownloadWorkflowScopeIdentity ( context )
556+ : this . getCurrentDownloadWorkflowScopeIdentity ( ) ;
557+ return workflowScope ? `${ workflowScope } ::${ missingKey } ` : missingKey ;
558+ } ,
559+
522560 rememberDownloadSnapshotForMissing ( missing , snapshot = { } ) {
523561 if ( ! missing ) return null ;
524562
525- const key = this . getDownloadStateKey ( missing ) ;
563+ const key = this . getDownloadStateKey ( missing , snapshot ) ;
526564 const store = this . getDownloadProgressStore ( ) ;
527565 const previous = store . get ( key ) || { } ;
528566 const progress = {
@@ -914,7 +952,11 @@ export const resolveDownloadMethods = {
914952 // started. Rebind it by the verified download/path match so the next
915953 // polling tick cannot repaint Local Matches from stale state.
916954 const activeEntry = this . activeDownloads ?. [ activeDownload . download_id ] ;
917- if ( activeEntry && activeEntry . missing !== missing ) {
955+ if (
956+ activeEntry
957+ && ( ! this . isDownloadInCurrentWorkflowScope || this . isDownloadInCurrentWorkflowScope ( activeEntry ) )
958+ && activeEntry . missing !== missing
959+ ) {
918960 activeEntry . missing = missing ;
919961 }
920962 }
@@ -995,6 +1037,49 @@ export const resolveDownloadMethods = {
9951037 return Array . isArray ( info . missing ?. matches ) ? info . missing . matches : [ ] ;
9961038 } ,
9971039
1040+ finalizeCancelledDownloadFrontend ( downloadId , info = { } , progress = { } ) {
1041+ if ( ! downloadId ) return ;
1042+
1043+ this . clearPendingDownloadStatus ?. ( info ) ;
1044+ this . removeCancelledDownloadLocalMatches ?. ( info , progress ) ;
1045+
1046+ const store = this . getDownloadProgressStore ?. ( ) ;
1047+ const toCancelledSnapshot = ( snapshot = { } ) => ( {
1048+ ...snapshot ,
1049+ downloadId,
1050+ missing : snapshot . missing || info . missing || null ,
1051+ progress : {
1052+ ...( snapshot . progress || { } ) ,
1053+ ...progress ,
1054+ status : 'cancelled' ,
1055+ speed : 0
1056+ } ,
1057+ status : 'cancelled' ,
1058+ message : 'Download cancelled - incomplete file removed' ,
1059+ type : 'warning' ,
1060+ isActive : false ,
1061+ updatedAt : Date . now ( )
1062+ } ) ;
1063+ let cancelledSnapshot = toCancelledSnapshot ( info . statusSnapshot || { } ) ;
1064+ if ( store instanceof Map ) {
1065+ for ( const [ key , snapshot ] of store . entries ( ) ) {
1066+ if ( String ( snapshot ?. downloadId || '' ) === String ( downloadId ) ) {
1067+ const terminalSnapshot = toCancelledSnapshot ( snapshot ) ;
1068+ store . set ( key , terminalSnapshot ) ;
1069+ cancelledSnapshot = terminalSnapshot ;
1070+ }
1071+ }
1072+ }
1073+
1074+ delete this . activeDownloads ?. [ downloadId ] ;
1075+
1076+ const elements = this . resolveDownloadUiElements ?. ( info ) || { } ;
1077+ this . renderDownloadSnapshot ?. ( downloadId , cancelledSnapshot , elements ) ;
1078+ this . refreshLocalMatchesUiForMissing ?. ( info . missing ) ;
1079+ this . updateDownloadAllButtonState ?. ( ) ;
1080+ this . updateQueuePanel ?. ( ) ;
1081+ } ,
1082+
9981083 persistLocalMatchesInAnalysisCache ( missing , matches = [ ] ) {
9991084 if ( ! missing || ! Array . isArray ( matches ) ) return ;
10001085
@@ -1094,10 +1179,14 @@ export const resolveDownloadMethods = {
10941179
10951180 getActiveDownloadEntryForMissing ( missing ) {
10961181 if ( ! missing ) return null ;
1097- const missingKey = this . getDownloadStateKey ( missing ) ;
1182+ const missingKey = this . getDownloadMissingIdentity ( missing ) ;
10981183
10991184 for ( const [ downloadId , info ] of Object . entries ( this . activeDownloads || { } ) ) {
1100- if ( info ?. missing && this . getDownloadStateKey ( info . missing ) === missingKey ) {
1185+ if (
1186+ info ?. missing
1187+ && this . isDownloadInCurrentWorkflowScope ( info )
1188+ && this . getDownloadMissingIdentity ( info . missing ) === missingKey
1189+ ) {
11011190 return { downloadId, info } ;
11021191 }
11031192 }
@@ -1106,9 +1195,13 @@ export const resolveDownloadMethods = {
11061195
11071196 getActiveDownloadEntriesForMissing ( missing ) {
11081197 if ( ! missing ) return [ ] ;
1109- const missingKey = this . getDownloadStateKey ( missing ) ;
1198+ const missingKey = this . getDownloadMissingIdentity ( missing ) ;
11101199 return Object . entries ( this . activeDownloads || { } )
1111- . filter ( ( [ , info ] ) => info ?. missing && this . getDownloadStateKey ( info . missing ) === missingKey )
1200+ . filter ( ( [ , info ] ) => (
1201+ info ?. missing
1202+ && this . isDownloadInCurrentWorkflowScope ( info )
1203+ && this . getDownloadMissingIdentity ( info . missing ) === missingKey
1204+ ) )
11121205 . map ( ( [ downloadId , info ] ) => ( { downloadId, info } ) ) ;
11131206 } ,
11141207
@@ -1883,14 +1976,16 @@ export const resolveDownloadMethods = {
18831976 desiredStatus === 'downloading' && currentStatus === 'paused'
18841977 ) || (
18851978 desiredStatus === 'paused' && ( currentStatus === 'downloading' || currentStatus === 'starting' )
1979+ ) || (
1980+ desiredStatus === 'cancelling' && [ 'starting' , 'downloading' , 'paused' ] . includes ( currentStatus )
18861981 ) ;
18871982 if ( ! canHoldDesiredStatus ) return progress ;
18881983
18891984 return {
18901985 ...progress ,
18911986 backend_status : currentStatus ,
18921987 status : desiredStatus ,
1893- speed : desiredStatus === 'paused' ? 0 : progress . speed
1988+ speed : desiredStatus === 'paused' || desiredStatus === 'cancelling' ? 0 : progress . speed
18941989 } ;
18951990 } ,
18961991
@@ -1903,6 +1998,9 @@ export const resolveDownloadMethods = {
19031998
19041999 try {
19052000 let progress = await this . fetchJson ( `/model_resolver/progress/${ downloadId } ` , { silent : true } , 'Get download progress' ) ;
2001+ // Cancel may have completed while this request was in flight. Never
2002+ // let a stale progress response recreate a globally finalized bar.
2003+ if ( this . activeDownloads ?. [ downloadId ] !== info ) return ;
19062004 progress = this . applyPendingDownloadStatus ( info , progress ) ;
19072005 const snapshot = this . rememberDownloadUiState ( downloadId , info , progress , { isActive : true } ) ;
19082006 const { progressDiv, downloadBtn } = this . resolveDownloadUiElements ( info ) ;
@@ -1921,6 +2019,12 @@ export const resolveDownloadMethods = {
19212019 // Continue polling
19222020 setTimeout ( ( ) => this . pollDownloadProgress ( downloadId ) , progress . status === 'paused' ? 1500 : 1000 ) ;
19232021
2022+ } else if ( progress . status === 'cancelling' ) {
2023+ this . renderDownloadSnapshot ( downloadId , snapshot , { progressDiv, downloadBtn } ) ;
2024+ this . refreshLocalMatchesUiForMissing ?. ( missing ) ;
2025+ this . updateQueuePanel ?. ( ) ;
2026+ setTimeout ( ( ) => this . pollDownloadProgress ( downloadId ) , 500 ) ;
2027+
19242028 } else if ( progress . status === 'completed' ) {
19252029 const alreadyExists = Boolean ( progress . already_exists ) ;
19262030 const completedSnapshot = this . rememberDownloadUiState ( downloadId , info , progress , {
@@ -1972,17 +2076,7 @@ export const resolveDownloadMethods = {
19722076 this . refreshLocalMatchesUiForMissing ?. ( missing ) ;
19732077
19742078 } else if ( progress . status === 'cancelled' ) {
1975- const cancelledSnapshot = this . rememberDownloadUiState ( downloadId , info , progress , {
1976- status : 'cancelled' ,
1977- type : 'warning' ,
1978- isActive : false
1979- } ) ;
1980- this . renderDownloadSnapshot ( downloadId , cancelledSnapshot , { progressDiv, downloadBtn } ) ;
1981- this . removeCancelledDownloadLocalMatches ?. ( info , progress ) ;
1982- delete this . activeDownloads [ downloadId ] ;
1983- this . updateDownloadAllButtonState ( ) ;
1984- this . updateQueuePanel ?. ( ) ;
1985- this . refreshLocalMatchesUiForMissing ?. ( missing ) ;
2079+ this . finalizeCancelledDownloadFrontend ?. ( downloadId , info , progress ) ;
19862080 this . showNotification ( 'Download cancelled' , 'info' ) ;
19872081
19882082 } else {
@@ -2033,7 +2127,7 @@ export const resolveDownloadMethods = {
20332127 cancelDownload ( downloadId ) {
20342128 const info = this . activeDownloads [ downloadId ] ;
20352129 if ( info ) {
2036- this . clearPendingDownloadStatus ( info ) ;
2130+ this . setPendingDownloadStatus ( info , 'cancelling' , 30000 ) ;
20372131 const snapshot = this . rememberDownloadUiState (
20382132 downloadId ,
20392133 info ,
@@ -2054,14 +2148,14 @@ export const resolveDownloadMethods = {
20542148 method : 'POST'
20552149 } , 'Cancel download' ) . then ( ( ) => {
20562150 if ( ! info ) return ;
2057- this . removeCancelledDownloadLocalMatches ?. ( info , {
2151+ this . finalizeCancelledDownloadFrontend ?. ( downloadId , info , {
20582152 ...( info . lastProgress || { } ) ,
20592153 status : 'cancelled' ,
20602154 filename : info . lastProgress ?. filename || info . filename || '' ,
20612155 path : info . lastProgress ?. path || info . downloadPath || '' ,
20622156 directory : info . lastProgress ?. directory || info . downloadDirectory || ''
20632157 } ) ;
2064- this . refreshLocalMatchesUiForMissing ?. ( info . missing ) ;
2158+ this . showNotification ( 'Download cancelled' , ' info' ) ;
20652159 } ) . catch ( ( error ) => {
20662160 console . error ( 'Model Resolver: Cancel error:' , error ) ;
20672161 this . showNotification ( 'Failed to cancel download' , 'error' ) ;
@@ -2070,6 +2164,29 @@ export const resolveDownloadMethods = {
20702164
20712165 pauseDownload ( downloadId ) {
20722166 const info = this . activeDownloads [ downloadId ] ;
2167+ const currentStatus = String (
2168+ info ?. pendingDownloadStatus
2169+ || info ?. lastStatus
2170+ || info ?. lastProgress ?. status
2171+ || ''
2172+ ) . toLowerCase ( ) ;
2173+ if ( currentStatus === 'cancelling' || currentStatus === 'cancelled' ) {
2174+ if ( info ) {
2175+ const snapshot = this . rememberDownloadUiState (
2176+ downloadId ,
2177+ info ,
2178+ { ...( info . lastProgress || { } ) , status : 'cancelling' , speed : 0 } ,
2179+ {
2180+ status : 'cancelling' ,
2181+ message : 'Cancelling download...' ,
2182+ type : 'info' ,
2183+ isActive : true
2184+ }
2185+ ) ;
2186+ this . renderDownloadSnapshot ( downloadId , snapshot , this . resolveDownloadUiElements ( info ) ) ;
2187+ }
2188+ return ;
2189+ }
20732190 const previousProgress = info ?. lastProgress ? { ...info . lastProgress } : null ;
20742191 if ( info ) {
20752192 this . setPendingDownloadStatus ( info , 'paused' ) ;
@@ -2095,6 +2212,24 @@ export const resolveDownloadMethods = {
20952212 this . showNotification ( 'Download paused' , 'info' ) ;
20962213 } ) . catch ( ( error ) => {
20972214 console . error ( 'Model Resolver: Pause error:' , error ) ;
2215+ const cancellationInProgress = / b e i n g c a n c e l l e d | c a n c e l l ? i n g / i. test ( String ( error ?. message || '' ) ) ;
2216+ if ( info && cancellationInProgress ) {
2217+ this . setPendingDownloadStatus ( info , 'cancelling' , 30000 ) ;
2218+ const snapshot = this . rememberDownloadUiState (
2219+ downloadId ,
2220+ info ,
2221+ { ...( info . lastProgress || { } ) , status : 'cancelling' , speed : 0 } ,
2222+ {
2223+ status : 'cancelling' ,
2224+ message : 'Cancelling download...' ,
2225+ type : 'info' ,
2226+ isActive : true
2227+ }
2228+ ) ;
2229+ this . renderDownloadSnapshot ( downloadId , snapshot , this . resolveDownloadUiElements ( info ) ) ;
2230+ this . updateQueuePanel ?. ( ) ;
2231+ return ;
2232+ }
20982233 if ( info && previousProgress ) {
20992234 this . clearPendingDownloadStatus ( info ) ;
21002235 const snapshot = this . rememberDownloadUiState (
0 commit comments