Skip to content

Commit 2865f0f

Browse files
authored
feat(ux): backend management enhancement (#9325)
* feat: add PreferDevelopmentBackends setting, expose isMeta/isDevelopment in API - Add PreferDevelopmentBackends config field, CLI flag, runtime setting - Add IsDevelopment() method to GalleryBackend - Use AvailableBackendsUnfiltered in UI API to show all backends - Expose isMeta, isDevelopment, preferDevelopmentBackends in backend API response * feat: upgrade banner with Upgrade All button, detect pre-existing backends - Add upgrade banner on Backends page showing count and Upgrade All button - Fix upgrade detection for backends installed before version tracking: flag as upgradeable when gallery has a version but installed has none - Fix OCI digest check to flag backends with no stored digest as upgradeable
1 parent 6fbda27 commit 2865f0f

11 files changed

Lines changed: 378 additions & 101 deletions

File tree

core/application/config_file_watcher.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,9 @@ func readRuntimeSettingsJson(startupAppConfig config.ApplicationConfig) fileHand
338338
if settings.AutoUpgradeBackends != nil {
339339
appConfig.AutoUpgradeBackends = *settings.AutoUpgradeBackends
340340
}
341+
if settings.PreferDevelopmentBackends != nil {
342+
appConfig.PreferDevelopmentBackends = *settings.PreferDevelopmentBackends
343+
}
341344
if settings.ApiKeys != nil {
342345
// API keys from env vars (startup) should be kept, runtime settings keys replace all runtime keys
343346
// If runtime_settings.json specifies ApiKeys (even if empty), it replaces all runtime keys

core/cli/run.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,9 @@ type RunCMD struct {
4747
BackendImagesReleaseTag string `env:"LOCALAI_BACKEND_IMAGES_RELEASE_TAG,BACKEND_IMAGES_RELEASE_TAG" help:"Fallback release tag for backend images" group:"backends" default:"latest"`
4848
BackendImagesBranchTag string `env:"LOCALAI_BACKEND_IMAGES_BRANCH_TAG,BACKEND_IMAGES_BRANCH_TAG" help:"Fallback branch tag for backend images" group:"backends" default:"master"`
4949
BackendDevSuffix string `env:"LOCALAI_BACKEND_DEV_SUFFIX,BACKEND_DEV_SUFFIX" help:"Development suffix for backend images" group:"backends" default:"development"`
50-
AutoUpgradeBackends bool `env:"LOCALAI_AUTO_UPGRADE_BACKENDS,AUTO_UPGRADE_BACKENDS" help:"Automatically upgrade backends when new versions are detected" group:"backends" default:"false"`
51-
PreloadModels string `env:"LOCALAI_PRELOAD_MODELS,PRELOAD_MODELS" help:"A List of models to apply in JSON at start" group:"models"`
50+
AutoUpgradeBackends bool `env:"LOCALAI_AUTO_UPGRADE_BACKENDS,AUTO_UPGRADE_BACKENDS" help:"Automatically upgrade backends when new versions are detected" group:"backends" default:"false"`
51+
PreferDevelopmentBackends bool `env:"LOCALAI_PREFER_DEV_BACKENDS,PREFER_DEV_BACKENDS" help:"Prefer development backend versions (shows development backends by default in UI)" group:"backends" default:"false"`
52+
PreloadModels string `env:"LOCALAI_PRELOAD_MODELS,PRELOAD_MODELS" help:"A List of models to apply in JSON at start" group:"models"`
5253
Models []string `env:"LOCALAI_MODELS,MODELS" help:"A List of model configuration URLs to load" group:"models"`
5354
PreloadModelsConfig string `env:"LOCALAI_PRELOAD_MODELS_CONFIG,PRELOAD_MODELS_CONFIG" help:"A List of models to apply at startup. Path to a YAML config file" group:"models"`
5455

@@ -495,6 +496,10 @@ func (r *RunCMD) Run(ctx *cliContext.Context) error {
495496
opts = append(opts, config.WithAutoUpgradeBackends(r.AutoUpgradeBackends))
496497
}
497498

499+
if r.PreferDevelopmentBackends {
500+
opts = append(opts, config.WithPreferDevelopmentBackends(r.PreferDevelopmentBackends))
501+
}
502+
498503
if r.PreloadBackendOnly {
499504
_, err := application.New(opts...)
500505
return err

core/config/application_config.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ type ApplicationConfig struct {
5858

5959
AutoloadGalleries, AutoloadBackendGalleries bool
6060
AutoUpgradeBackends bool
61+
PreferDevelopmentBackends bool
6162

6263
SingleBackend bool // Deprecated: use MaxActiveBackends = 1 instead
6364
MaxActiveBackends int // Maximum number of active backends (0 = unlimited, 1 = single backend mode)
@@ -395,6 +396,10 @@ func WithAutoUpgradeBackends(v bool) AppOption {
395396
return func(o *ApplicationConfig) { o.AutoUpgradeBackends = v }
396397
}
397398

399+
func WithPreferDevelopmentBackends(v bool) AppOption {
400+
return func(o *ApplicationConfig) { o.PreferDevelopmentBackends = v }
401+
}
402+
398403
var EnableFederated = func(o *ApplicationConfig) {
399404
o.Federated = true
400405
}
@@ -868,6 +873,7 @@ func (o *ApplicationConfig) ToRuntimeSettings() RuntimeSettings {
868873
autoloadGalleries := o.AutoloadGalleries
869874
autoloadBackendGalleries := o.AutoloadBackendGalleries
870875
autoUpgradeBackends := o.AutoUpgradeBackends
876+
preferDevelopmentBackends := o.PreferDevelopmentBackends
871877
apiKeys := o.ApiKeys
872878
agentJobRetentionDays := o.AgentJobRetentionDays
873879

@@ -942,6 +948,7 @@ func (o *ApplicationConfig) ToRuntimeSettings() RuntimeSettings {
942948
AutoloadGalleries: &autoloadGalleries,
943949
AutoloadBackendGalleries: &autoloadBackendGalleries,
944950
AutoUpgradeBackends: &autoUpgradeBackends,
951+
PreferDevelopmentBackends: &preferDevelopmentBackends,
945952
ApiKeys: &apiKeys,
946953
AgentJobRetentionDays: &agentJobRetentionDays,
947954
OpenResponsesStoreTTL: &openResponsesStoreTTL,
@@ -1093,6 +1100,9 @@ func (o *ApplicationConfig) ApplyRuntimeSettings(settings *RuntimeSettings) (req
10931100
if settings.AutoUpgradeBackends != nil {
10941101
o.AutoUpgradeBackends = *settings.AutoUpgradeBackends
10951102
}
1103+
if settings.PreferDevelopmentBackends != nil {
1104+
o.PreferDevelopmentBackends = *settings.PreferDevelopmentBackends
1105+
}
10961106
if settings.AgentJobRetentionDays != nil {
10971107
o.AgentJobRetentionDays = *settings.AgentJobRetentionDays
10981108
}

core/config/runtime_settings.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ type RuntimeSettings struct {
2020
// Backend management
2121
SingleBackend *bool `json:"single_backend,omitempty"` // Deprecated: use MaxActiveBackends = 1 instead
2222
MaxActiveBackends *int `json:"max_active_backends,omitempty"` // Maximum number of active backends (0 = unlimited, 1 = single backend mode)
23-
AutoUpgradeBackends *bool `json:"auto_upgrade_backends,omitempty"` // Automatically upgrade backends when new versions are detected
23+
AutoUpgradeBackends *bool `json:"auto_upgrade_backends,omitempty"` // Automatically upgrade backends when new versions are detected
24+
PreferDevelopmentBackends *bool `json:"prefer_development_backends,omitempty"` // Prefer development backend versions by default in UI
2425
// Memory Reclaimer settings (works with GPU if available, otherwise RAM)
2526
MemoryReclaimerEnabled *bool `json:"memory_reclaimer_enabled,omitempty"` // Enable memory threshold monitoring
2627
MemoryReclaimerThreshold *float64 `json:"memory_reclaimer_threshold,omitempty"` // Threshold 0.0-1.0 (e.g., 0.95 = 95%)

core/gallery/backend_types.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package gallery
22

33
import (
44
"fmt"
5+
"strings"
56

67
"github.com/mudler/LocalAI/core/config"
78
"github.com/mudler/LocalAI/pkg/system"
@@ -70,6 +71,13 @@ func (m *GalleryBackend) IsMeta() bool {
7071
return len(m.CapabilitiesMap) > 0 && m.URI == ""
7172
}
7273

74+
func (m *GalleryBackend) IsDevelopment(devSuffix string) bool {
75+
if devSuffix == "" {
76+
devSuffix = defaultDevSuffix
77+
}
78+
return strings.HasSuffix(m.Name, "-"+devSuffix)
79+
}
80+
7381
// IsCompatibleWith checks if the backend is compatible with the current system capability.
7482
// For meta backends, it checks if any of the capabilities in the map match the system capability.
7583
// For concrete backends, it delegates to SystemState.IsBackendCompatible.

core/gallery/upgrade.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,22 +72,36 @@ func CheckBackendUpgrades(ctx context.Context, galleries []config.Gallery, syste
7272
continue
7373
}
7474

75-
// If either version is empty, fall back to OCI digest comparison
76-
if installed.Metadata.Digest != "" && downloader.URI(galleryEntry.URI).LooksLikeOCI() {
75+
// Gallery has a version but installed doesn't — this happens for backends
76+
// installed before version tracking was added. Flag as upgradeable so
77+
// users can re-install to pick up version metadata.
78+
if galleryVersion != "" && installedVersion == "" {
79+
result[installed.Metadata.Name] = UpgradeInfo{
80+
BackendName: installed.Metadata.Name,
81+
InstalledVersion: "",
82+
AvailableVersion: galleryVersion,
83+
}
84+
continue
85+
}
86+
87+
// Fall back to OCI digest comparison when versions are unavailable
88+
if downloader.URI(galleryEntry.URI).LooksLikeOCI() {
7789
remoteDigest, err := oci.GetImageDigest(galleryEntry.URI, "", nil, nil)
7890
if err != nil {
7991
xlog.Warn("Failed to get remote OCI digest for upgrade check", "backend", installed.Metadata.Name, "error", err)
8092
continue
8193
}
82-
if remoteDigest != installed.Metadata.Digest {
94+
// If we have a stored digest, compare; otherwise any remote digest
95+
// means we can't confirm we're up to date — flag as upgradeable
96+
if installed.Metadata.Digest == "" || remoteDigest != installed.Metadata.Digest {
8397
result[installed.Metadata.Name] = UpgradeInfo{
8498
BackendName: installed.Metadata.Name,
8599
InstalledDigest: installed.Metadata.Digest,
86100
AvailableDigest: remoteDigest,
87101
}
88102
}
89103
}
90-
// No version info and no digest to compare — skip
104+
// No version info and non-OCI URI — cannot determine, skip
91105
}
92106

93107
return result, nil

core/http/react-ui/package-lock.json

Lines changed: 164 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)