feat(models): add model storage size display and RAM warning (#8675)

Add model storage size display and RAM warning in Models tab

- Backend (ui_api.go):
  - Added getDirectorySize() helper function to calculate total size of model files
  - Added storageSize, ramTotal, ramUsed, ramUsagePercent to /api/models endpoint response
  - Uses xsysinfo.GetSystemRAMInfo() for RAM information

- Frontend (models.html):
  - Added storageSize, ramTotal, ramUsed, ramUsagePercent to Alpine.js data object
  - Added formatBytes() helper for human-readable byte formatting
  - Display storage size in hero header with blue indicator
  - Show warning banner when storage exceeds RAM (model too large for system)

Addresses: https://github.com/mudler/LocalAI/issues/6251

Signed-off-by: localai-bot <localai-bot@users.noreply.github.com>
Co-authored-by: localai-bot <localai-bot@users.noreply.github.com>
This commit is contained in:
LocalAI [bot]
2026-02-28 22:05:01 +01:00
committed by GitHub
parent b647b6caf1
commit b10443ab5a
2 changed files with 56 additions and 0 deletions

View File

@@ -1,5 +1,7 @@
package routes
import "os"
import (
"context"
"fmt"
@@ -32,6 +34,25 @@ const (
ascSortOrder = "asc"
)
// getDirectorySize calculates the total size of files in a directory
func getDirectorySize(path string) (int64, error) {
var totalSize int64
entries, err := os.ReadDir(path)
if err != nil {
return 0, err
}
for _, entry := range entries {
info, err := entry.Info()
if err != nil {
continue
}
if !info.IsDir() {
totalSize += info.Size()
}
}
return totalSize, nil
}
// RegisterUIAPIRoutes registers JSON API routes for the web UI
func RegisterUIAPIRoutes(app *echo.Echo, cl *config.ModelConfigLoader, ml *model.ModelLoader, appConfig *config.ApplicationConfig, galleryService *services.GalleryService, opcache *services.OpCache, applicationInstance *application.Application) {
@@ -297,6 +318,12 @@ func RegisterUIAPIRoutes(app *echo.Echo, cl *config.ModelConfigLoader, ml *model
modelsWithoutConfig, _ := services.ListModels(cl, ml, config.NoFilterFn, services.LOOSE_ONLY)
installedModelsCount := len(modelConfigs) + len(modelsWithoutConfig)
// Calculate storage size and RAM info
modelsPath := appConfig.SystemState.Model.ModelsPath
storageSize, _ := getDirectorySize(modelsPath)
ramInfo, _ := xsysinfo.GetSystemRAMInfo()
return c.JSON(200, map[string]interface{}{
"models": modelsJSON,
"repositories": appConfig.Galleries,
@@ -305,6 +332,10 @@ func RegisterUIAPIRoutes(app *echo.Echo, cl *config.ModelConfigLoader, ml *model
"taskTypes": taskTypes,
"availableModels": totalModels,
"installedModels": installedModelsCount,
"storageSize": storageSize,
"ramTotal": ramInfo.Total,
"ramUsed": ramInfo.Used,
"ramUsagePercent": ramInfo.UsagePercent,
"currentPage": pageNum,
"totalPages": totalPages,
"prevPage": prevPage,

View File

@@ -61,6 +61,15 @@
<span class="font-semibold text-purple-300" x-text="repositories.length"></span>
<span class="text-[var(--color-text-secondary)] ml-1">repositories</span>
</div>
<div class="flex items-center bg-[var(--color-bg-primary)] rounded-lg px-4 py-2">
<div class="w-2 h-2 bg-blue-500 rounded-full mr-2"></div>
<span class="font-semibold text-blue-300" x-text="formatBytes(storageSize)"></span>
<span class="text-[var(--color-text-secondary)] ml-1">storage</span>
</div>
<div x-show="storageSize > ramTotal" class="flex items-center bg-red-500/20 rounded-lg px-4 py-2 border border-red-500/30">
<i class="fas fa-exclamation-triangle text-red-400 mr-2"></i>
<span class="text-red-300 text-sm">Storage exceeds RAM!</span>
</div>
<a href="/import-model" class="inline-flex items-center gap-1.5 text-xs text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] bg-transparent hover:bg-[var(--color-primary)]/10 border border-[var(--color-border-subtle)] hover:border-[var(--color-primary)]/30 rounded-md py-1.5 px-2.5 transition-colors">
<i class="fas fa-upload"></i>
<span>Import Model</span>
@@ -605,6 +614,10 @@ function modelsGallery() {
totalPages: 1,
availableModels: 0,
installedModels: 0,
storageSize: 0,
ramTotal: 0,
ramUsed: 0,
ramUsagePercent: 0,
selectedModel: null,
jobProgress: {},
notifications: [],
@@ -650,6 +663,10 @@ function modelsGallery() {
this.totalPages = data.totalPages || 1;
this.availableModels = data.availableModels || 0;
this.installedModels = data.installedModels || 0;
this.storageSize = data.storageSize || 0;
this.ramTotal = data.ramTotal || 0;
this.ramUsed = data.ramUsed || 0;
this.ramUsagePercent = data.ramUsagePercent || 0;
} catch (error) {
console.error('Error fetching models:', error);
} finally {
@@ -826,6 +843,14 @@ function modelsGallery() {
this.selectedModel = model;
},
formatBytes(bytes) {
if (bytes === 0) return "0 B";
const k = 1024;
const sizes = ["B", "KB", "MB", "GB", "TB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
},
closeModal() {
this.selectedModel = null;
}