mirror of
https://github.com/mudler/LocalAI.git
synced 2026-03-31 21:25:59 -04:00
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:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user