@@ -177,6 +177,49 @@ export default class llamacpp_extension extends AIEngine {
177177 this . configureBackends ( )
178178 }
179179
180+ private getStoredBackendType ( ) : string | null {
181+ try {
182+ return localStorage . getItem ( 'llama_cpp_backend_type' )
183+ } catch ( error ) {
184+ logger . warn ( 'Failed to read backend type from localStorage:' , error )
185+ return null
186+ }
187+ }
188+
189+ private setStoredBackendType ( backendType : string ) : void {
190+ try {
191+ localStorage . setItem ( 'llama_cpp_backend_type' , backendType )
192+ logger . info ( `Stored backend type preference: ${ backendType } ` )
193+ } catch ( error ) {
194+ logger . warn ( 'Failed to store backend type in localStorage:' , error )
195+ }
196+ }
197+
198+ private clearStoredBackendType ( ) : void {
199+ try {
200+ localStorage . removeItem ( 'llama_cpp_backend_type' )
201+ logger . info ( 'Cleared stored backend type preference' )
202+ } catch ( error ) {
203+ logger . warn ( 'Failed to clear backend type from localStorage:' , error )
204+ }
205+ }
206+
207+ private findLatestVersionForBackend (
208+ version_backends : { version : string ; backend : string } [ ] ,
209+ backendType : string
210+ ) : string | null {
211+ const matchingBackends = version_backends . filter (
212+ ( vb ) => vb . backend === backendType
213+ )
214+ if ( matchingBackends . length === 0 ) {
215+ return null
216+ }
217+
218+ // Sort by version (newest first) and get the latest
219+ matchingBackends . sort ( ( a , b ) => b . version . localeCompare ( a . version ) )
220+ return `${ matchingBackends [ 0 ] . version } /${ matchingBackends [ 0 ] . backend } `
221+ }
222+
180223 async configureBackends ( ) : Promise < void > {
181224 if ( this . isConfiguringBackends ) {
182225 logger . info (
@@ -207,8 +250,33 @@ export default class llamacpp_extension extends AIEngine {
207250 )
208251 }
209252
210- let bestAvailableBackendString =
211- this . determineBestBackend ( version_backends )
253+ // Get stored backend preference
254+ const storedBackendType = this . getStoredBackendType ( )
255+ let bestAvailableBackendString = ''
256+
257+ if ( storedBackendType ) {
258+ // Find the latest version of the stored backend type
259+ const preferredBackendString = this . findLatestVersionForBackend (
260+ version_backends ,
261+ storedBackendType
262+ )
263+ if ( preferredBackendString ) {
264+ bestAvailableBackendString = preferredBackendString
265+ logger . info (
266+ `Using stored backend preference: ${ bestAvailableBackendString } `
267+ )
268+ } else {
269+ logger . warn (
270+ `Stored backend type '${ storedBackendType } ' not available, falling back to best backend`
271+ )
272+ // Clear the invalid stored preference
273+ this . clearStoredBackendType ( )
274+ bestAvailableBackendString =
275+ this . determineBestBackend ( version_backends )
276+ }
277+ } else {
278+ bestAvailableBackendString = this . determineBestBackend ( version_backends )
279+ }
212280
213281 let settings = structuredClone ( SETTINGS )
214282 const backendSettingIndex = settings . findIndex (
@@ -231,11 +299,30 @@ export default class llamacpp_extension extends AIEngine {
231299 originalDefaultBackendValue
232300 )
233301
234- const initialUiDefault =
302+ // Determine initial UI default based on priority:
303+ // 1. Saved setting (if valid and not original default)
304+ // 2. Best available for stored backend type
305+ // 3. Original default
306+ let initialUiDefault = originalDefaultBackendValue
307+
308+ if (
235309 savedBackendSetting &&
236310 savedBackendSetting !== originalDefaultBackendValue
237- ? savedBackendSetting
238- : bestAvailableBackendString || originalDefaultBackendValue
311+ ) {
312+ initialUiDefault = savedBackendSetting
313+ // Store the backend type from the saved setting
314+ const [ , backendType ] = savedBackendSetting . split ( '/' )
315+ if ( backendType ) {
316+ this . setStoredBackendType ( backendType )
317+ }
318+ } else if ( bestAvailableBackendString ) {
319+ initialUiDefault = bestAvailableBackendString
320+ // Store the backend type from the best available
321+ const [ , backendType ] = bestAvailableBackendString . split ( '/' )
322+ if ( backendType ) {
323+ this . setStoredBackendType ( backendType )
324+ }
325+ }
239326
240327 backendSetting . controllerProps . value = initialUiDefault
241328 logger . info (
@@ -253,6 +340,37 @@ export default class llamacpp_extension extends AIEngine {
253340 let effectiveBackendString = this . config . version_backend
254341 let backendWasDownloaded = false
255342
343+ // Handle fresh installation case where version_backend might be 'none' or invalid
344+ if (
345+ ! effectiveBackendString ||
346+ effectiveBackendString === 'none' ||
347+ ! effectiveBackendString . includes ( '/' )
348+ ) {
349+ effectiveBackendString = bestAvailableBackendString
350+ logger . info (
351+ `Fresh installation or invalid backend detected, using: ${ effectiveBackendString } `
352+ )
353+
354+ // Update the config immediately
355+ this . config . version_backend = effectiveBackendString
356+ }
357+
358+ // Download and install the backend if not already present
359+ if ( effectiveBackendString ) {
360+ const [ version , backend ] = effectiveBackendString . split ( '/' )
361+ if ( version && backend ) {
362+ const isInstalled = await isBackendInstalled ( backend , version )
363+ if ( ! isInstalled ) {
364+ logger . info ( `Installing initial backend: ${ effectiveBackendString } ` )
365+ await this . ensureBackendReady ( backend , version )
366+ backendWasDownloaded = true
367+ logger . info (
368+ `Successfully installed initial backend: ${ effectiveBackendString } `
369+ )
370+ }
371+ }
372+ }
373+
256374 if ( this . config . auto_update_engine ) {
257375 const updateResult = await this . handleAutoUpdate (
258376 bestAvailableBackendString
@@ -263,12 +381,8 @@ export default class llamacpp_extension extends AIEngine {
263381 }
264382 }
265383
266- if ( ! backendWasDownloaded ) {
384+ if ( ! backendWasDownloaded && effectiveBackendString ) {
267385 await this . ensureFinalBackendInstallation ( effectiveBackendString )
268- } else {
269- logger . info (
270- 'Skipping final installation check - backend was just downloaded during auto-update'
271- )
272386 }
273387 } finally {
274388 this . isConfiguringBackends = false
@@ -350,65 +464,141 @@ export default class llamacpp_extension extends AIEngine {
350464 return { wasUpdated : false , newBackend : this . config . version_backend }
351465 }
352466
467+ // If version_backend is empty, invalid, or 'none', use the best available backend
468+ if (
469+ ! this . config . version_backend ||
470+ this . config . version_backend === '' ||
471+ this . config . version_backend === 'none' ||
472+ ! this . config . version_backend . includes ( '/' )
473+ ) {
474+ logger . info (
475+ 'No valid backend currently selected, using best available backend'
476+ )
477+ try {
478+ const [ bestVersion , bestBackend ] = bestAvailableBackendString . split ( '/' )
479+
480+ // Download new backend
481+ await this . ensureBackendReady ( bestBackend , bestVersion )
482+
483+ // Add delay on Windows
484+ if ( IS_WINDOWS ) {
485+ await new Promise ( ( resolve ) => setTimeout ( resolve , 1000 ) )
486+ }
487+
488+ // Update configuration
489+ this . config . version_backend = bestAvailableBackendString
490+
491+ // Store the backend type preference
492+ this . setStoredBackendType ( bestBackend )
493+
494+ // Update settings
495+ const settings = await this . getSettings ( )
496+ await this . updateSettings (
497+ settings . map ( ( item ) => {
498+ if ( item . key === 'version_backend' ) {
499+ item . controllerProps . value = bestAvailableBackendString
500+ }
501+ return item
502+ } )
503+ )
504+
505+ logger . info (
506+ `Successfully set initial backend: ${ bestAvailableBackendString } `
507+ )
508+ return { wasUpdated : true , newBackend : bestAvailableBackendString }
509+ } catch ( error ) {
510+ logger . error ( 'Failed to set initial backend:' , error )
511+ return { wasUpdated : false , newBackend : this . config . version_backend }
512+ }
513+ }
514+
515+ // Parse current backend configuration
353516 const [ currentVersion , currentBackend ] = (
354517 this . config . version_backend || ''
355518 ) . split ( '/' )
356- const [ bestVersion , bestBackend ] = bestAvailableBackendString . split ( '/' )
357519
358- // Check if update is needed
359- if ( currentBackend === bestBackend && currentVersion === bestVersion ) {
360- logger . info ( 'Auto-update: Already using the best available backend' )
520+ if ( ! currentVersion || ! currentBackend ) {
521+ logger . warn (
522+ `Invalid current backend format: ${ this . config . version_backend } `
523+ )
361524 return { wasUpdated : false , newBackend : this . config . version_backend }
362525 }
363526
364- // Perform update
527+ // Find the latest version for the currently selected backend type
528+ const version_backends = await listSupportedBackends ( )
529+ const targetBackendString = this . findLatestVersionForBackend (
530+ version_backends ,
531+ currentBackend
532+ )
533+
534+ if ( ! targetBackendString ) {
535+ logger . warn (
536+ `No available versions found for current backend type: ${ currentBackend } `
537+ )
538+ return { wasUpdated : false , newBackend : this . config . version_backend }
539+ }
540+
541+ const [ latestVersion ] = targetBackendString . split ( '/' )
542+
543+ // Check if update is needed (only version comparison for same backend type)
544+ if ( currentVersion === latestVersion ) {
545+ logger . info (
546+ 'Auto-update: Already using the latest version of the selected backend'
547+ )
548+ return { wasUpdated : false , newBackend : this . config . version_backend }
549+ }
550+
551+ // Perform version update for the same backend type
365552 try {
366553 logger . info (
367- `Auto-updating from ${ this . config . version_backend } to ${ bestAvailableBackendString } `
554+ `Auto-updating from ${ this . config . version_backend } to ${ targetBackendString } (preserving backend type) `
368555 )
369556
370- // Download new backend first
371- await this . ensureBackendReady ( bestBackend , bestVersion )
557+ // Download new version of the same backend type
558+ await this . ensureBackendReady ( currentBackend , latestVersion )
372559
373- // Add a small delay on Windows to ensure file operations complete
560+ // Add delay on Windows
374561 if ( IS_WINDOWS ) {
375562 await new Promise ( ( resolve ) => setTimeout ( resolve , 1000 ) )
376563 }
377564
378565 // Update configuration
379- this . config . version_backend = bestAvailableBackendString
566+ this . config . version_backend = targetBackendString
567+
568+ // Update stored backend type preference (in case it changed)
569+ this . setStoredBackendType ( currentBackend )
380570
381571 // Update settings
382572 const settings = await this . getSettings ( )
383573 await this . updateSettings (
384574 settings . map ( ( item ) => {
385575 if ( item . key === 'version_backend' ) {
386- item . controllerProps . value = bestAvailableBackendString
576+ item . controllerProps . value = targetBackendString
387577 }
388578 return item
389579 } )
390580 )
391581
392582 logger . info (
393- `Successfully updated to backend: ${ bestAvailableBackendString } `
583+ `Successfully updated to backend: ${ targetBackendString } (preserved backend type: ${ currentBackend } ) `
394584 )
395585
396- // Clean up old backends (with additional delay on Windows)
586+ // Clean up old versions of the same backend type
397587 if ( IS_WINDOWS ) {
398588 await new Promise ( ( resolve ) => setTimeout ( resolve , 500 ) )
399589 }
400- await this . removeOldBackends ( bestVersion , bestBackend )
590+ await this . removeOldBackend ( latestVersion , currentBackend )
401591
402- return { wasUpdated : true , newBackend : bestAvailableBackendString }
592+ return { wasUpdated : true , newBackend : targetBackendString }
403593 } catch ( error ) {
404594 logger . error ( 'Auto-update failed:' , error )
405595 return { wasUpdated : false , newBackend : this . config . version_backend }
406596 }
407597 }
408598
409- private async removeOldBackends (
410- bestVersion : string ,
411- bestBackend : string
599+ private async removeOldBackend (
600+ latestVersion : string ,
601+ backendType : string
412602 ) : Promise < void > {
413603 try {
414604 const janDataFolderPath = await getJanDataFolderPath ( )
@@ -426,32 +616,35 @@ export default class llamacpp_extension extends AIEngine {
426616
427617 for ( const versionDir of versionDirs ) {
428618 const versionPath = await joinPath ( [ backendsDir , versionDir ] )
429- const backendTypeDirs = await fs . readdirSync ( versionPath )
619+ const versionName = await basename ( versionDir )
430620
431- for ( const backendTypeDir of backendTypeDirs ) {
432- const versionName = await basename ( versionDir )
433- const backendName = await basename ( backendTypeDir )
621+ // Skip the latest version
622+ if ( versionName === latestVersion ) {
623+ continue
624+ }
434625
435- // Skip if it's the best version/backend
436- if ( versionName === bestVersion && backendName === bestBackend ) {
437- continue
438- }
626+ // Check if this version has the specific backend type we're interested in
627+ const backendTypePath = await joinPath ( [ versionPath , backendType ] )
439628
440- // If this other backend is installed, remove it
441- const isInstalled = await isBackendInstalled ( backendName , versionName )
629+ if ( await fs . existsSync ( backendTypePath ) ) {
630+ const isInstalled = await isBackendInstalled ( backendType , versionName )
442631 if ( isInstalled ) {
443- const toRemove = await joinPath ( [ versionPath , backendTypeDir ] )
444632 try {
445- await fs . rm ( toRemove )
446- logger . info ( `Removed old backend: ${ toRemove } ` )
633+ await fs . rm ( backendTypePath )
634+ logger . info (
635+ `Removed old version of ${ backendType } : ${ backendTypePath } `
636+ )
447637 } catch ( e ) {
448- logger . warn ( `Failed to remove old backend: ${ toRemove } ` , e )
638+ logger . warn (
639+ `Failed to remove old backend version: ${ backendTypePath } ` ,
640+ e
641+ )
449642 }
450643 }
451644 }
452645 }
453646 } catch ( error ) {
454- logger . error ( 'Error during old backend cleanup:' , error )
647+ logger . error ( 'Error during old backend version cleanup:' , error )
455648 }
456649 }
457650
@@ -526,6 +719,12 @@ export default class llamacpp_extension extends AIEngine {
526719 const valueStr = value as string
527720 const [ version , backend ] = valueStr . split ( '/' )
528721
722+ // Store the backend type preference in localStorage
723+ if ( backend ) {
724+ this . setStoredBackendType ( backend )
725+ logger . info ( `Updated backend type preference to: ${ backend } ` )
726+ }
727+
529728 // Reset device setting when backend changes
530729 this . config . device = ''
531730
0 commit comments