mirror of
https://github.com/mudler/LocalAI.git
synced 2026-05-18 05:33:09 -04:00
feat(ui): add system backend metadata and deletion in index (#6546)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
committed by
GitHub
parent
a22f6a499d
commit
a1b056737a
@@ -620,6 +620,32 @@ func RegisterUIAPIRoutes(app *fiber.App, cl *config.ModelConfigLoader, appConfig
|
||||
return c.JSON(response)
|
||||
})
|
||||
|
||||
// System Backend Deletion API (for installed backends on index page)
|
||||
app.Post("/api/backends/system/delete/:name", func(c *fiber.Ctx) error {
|
||||
backendName := strings.Clone(c.Params("name"))
|
||||
// URL decode the backend name
|
||||
backendName, err := url.QueryUnescape(backendName)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": "invalid backend name",
|
||||
})
|
||||
}
|
||||
log.Debug().Msgf("API request to delete system backend: %+v\n", backendName)
|
||||
|
||||
// Use the gallery package to delete the backend
|
||||
if err := gallery.DeleteBackendFromSystem(appConfig.SystemState, backendName); err != nil {
|
||||
log.Error().Err(err).Msgf("Failed to delete backend: %s", backendName)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"success": true,
|
||||
"message": "Backend deleted successfully",
|
||||
})
|
||||
})
|
||||
|
||||
// P2P APIs
|
||||
app.Get("/api/p2p/workers", func(c *fiber.Ctx) error {
|
||||
nodes := p2p.GetAvailableNodes(p2p.NetworkID(appConfig.P2PNetworkID, p2p.WorkerID))
|
||||
|
||||
@@ -3,10 +3,35 @@
|
||||
{{template "views/partials/head" .}}
|
||||
|
||||
<body class="bg-[#101827] text-[#E5E7EB]">
|
||||
<div class="flex flex-col min-h-screen">
|
||||
<div class="flex flex-col min-h-screen" x-data="indexDashboard()">
|
||||
|
||||
{{template "views/partials/navbar" .}}
|
||||
|
||||
<!-- Notifications -->
|
||||
<div class="fixed top-20 right-4 z-50 space-y-2" style="max-width: 400px;">
|
||||
<template x-for="notification in notifications" :key="notification.id">
|
||||
<div x-show="true"
|
||||
x-transition:enter="transform ease-out duration-300 transition"
|
||||
x-transition:enter-start="translate-x-full opacity-0"
|
||||
x-transition:enter-end="translate-x-0 opacity-100"
|
||||
x-transition:leave="transform ease-in duration-200 transition"
|
||||
x-transition:leave-start="translate-x-0 opacity-100"
|
||||
x-transition:leave-end="translate-x-full opacity-0"
|
||||
:class="notification.type === 'error' ? 'bg-red-500' : 'bg-green-500'"
|
||||
class="rounded-lg shadow-xl p-4 text-white flex items-start space-x-3">
|
||||
<div class="flex-shrink-0">
|
||||
<i :class="notification.type === 'error' ? 'fas fa-exclamation-circle' : 'fas fa-check-circle'" class="text-xl"></i>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-medium break-words" x-text="notification.message"></p>
|
||||
</div>
|
||||
<button @click="dismissNotification(notification.id)" class="flex-shrink-0 text-white hover:text-gray-200">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="container mx-auto px-4 py-8 flex-grow">
|
||||
<!-- Hero Section -->
|
||||
<div class="relative bg-[#1E293B] border border-[#38BDF8]/20 rounded-3xl shadow-2xl shadow-[#38BDF8]/10 p-8 mb-12 overflow-hidden">
|
||||
@@ -62,7 +87,7 @@
|
||||
|
||||
{{ if eq (len .ModelsConfig) 0 }}
|
||||
<!-- No Models State -->
|
||||
<div class="relative bg-[#1E293B]/80 border border-[#1E293B] rounded-2xl p-12 shadow-xl backdrop-blur-sm">
|
||||
<div class="relative bg-[#1E293B]/80 border border-[#38BDF8]/20 rounded-2xl p-12 shadow-xl backdrop-blur-sm">
|
||||
<div class="absolute inset-0 rounded-2xl bg-gradient-to-br from-yellow-500/5 to-orange-500/5"></div>
|
||||
<div class="relative text-center max-w-4xl mx-auto">
|
||||
<div class="inline-flex items-center justify-center w-20 h-20 rounded-full bg-yellow-500/10 border border-yellow-500/20 mb-6">
|
||||
@@ -83,12 +108,12 @@
|
||||
</div>
|
||||
|
||||
{{ if ne (len .Models) 0 }}
|
||||
<div class="mt-12 pt-8 border-t border-[#1E293B]">
|
||||
<div class="mt-12 pt-8 border-t border-[#38BDF8]/20">
|
||||
<h3 class="text-2xl font-bold text-[#E5E7EB] mb-6">Detected Model Files</h3>
|
||||
<p class="text-[#94A3B8] mb-6">These models were found but don't have configuration files yet</p>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{{ range .Models }}
|
||||
<div class="bg-[#101827] border border-[#1E293B] rounded-xl p-4 flex items-center hover:border-[#38BDF8]/50 transition-colors hover:shadow-[0_0_12px_rgba(56,189,248,0.15)]">
|
||||
<div class="bg-[#101827] border border-[#38BDF8]/20 rounded-xl p-4 flex items-center hover:border-[#38BDF8]/50 transition-all duration-300 hover:shadow-[0_0_12px_rgba(56,189,248,0.15)]">
|
||||
<div class="w-10 h-10 rounded-lg bg-[#1E293B] flex items-center justify-center mr-3">
|
||||
<i class="fas fa-brain text-[#38BDF8]"></i>
|
||||
</div>
|
||||
@@ -126,7 +151,7 @@
|
||||
{{ range .ModelsConfig }}
|
||||
{{ $backendCfg := . }}
|
||||
{{ $cfg:= index $galleryConfig .Name}}
|
||||
<div class="group relative bg-[#1E293B] border border-[#1E293B] rounded-2xl overflow-hidden transition-all duration-500 hover:shadow-[0_0_20px_rgba(56,189,248,0.2)] hover:-translate-y-2 hover:border-[#38BDF8]/50">
|
||||
<div class="group relative bg-[#1E293B] border border-[#38BDF8]/20 rounded-2xl overflow-hidden transition-all duration-500 hover:shadow-[0_0_20px_rgba(56,189,248,0.2)] hover:-translate-y-2 hover:border-[#38BDF8]/50">
|
||||
<!-- Card Header -->
|
||||
<div class="relative p-6 border-b border-[#101827]">
|
||||
<div class="flex items-start space-x-4">
|
||||
@@ -232,7 +257,7 @@
|
||||
|
||||
<!-- Models without config -->
|
||||
{{ range .Models }}
|
||||
<div class="group relative bg-[#1E293B]/80 border border-[#1E293B] rounded-2xl overflow-hidden transition-all duration-500 hover:shadow-[0_0_15px_rgba(234,179,8,0.15)] hover:-translate-y-1 hover:border-yellow-500/30">
|
||||
<div class="group relative bg-[#1E293B]/80 border border-[#38BDF8]/20 rounded-2xl overflow-hidden transition-all duration-500 hover:shadow-[0_0_15px_rgba(234,179,8,0.15)] hover:-translate-y-1 hover:border-yellow-500/30">
|
||||
<div class="p-6">
|
||||
<div class="flex items-start space-x-4">
|
||||
<div class="w-16 h-16 rounded-xl overflow-hidden flex-shrink-0 bg-[#101827] flex items-center justify-center">
|
||||
@@ -265,55 +290,201 @@
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<div class="mt-8 flex flex-col md:flex-row md:items-center md:justify-between">
|
||||
<div class="mb-4 md:mb-0">
|
||||
<!-- Backends Section -->
|
||||
<div class="mt-12">
|
||||
<div class="mb-8">
|
||||
<h2 class="text-3xl md:text-4xl font-bold text-[#E5E7EB] mb-2">
|
||||
Installed Backends
|
||||
</h2>
|
||||
<p class="text-[#94A3B8]">
|
||||
<span class="text-[#38BDF8] font-semibold">{{len .InstalledBackends}}</span> backend{{if gt (len .InstalledBackends) 1}}s{{end}} ready to use
|
||||
<span class="text-[#8B5CF6] font-semibold">{{len .InstalledBackends}}</span> backend{{if gt (len .InstalledBackends) 1}}s{{end}} ready to use
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
|
||||
|
||||
|
||||
{{ if ne (len .InstalledBackends) 0 }}
|
||||
<!-- No backends, suggest to install one -->
|
||||
{{ else }}
|
||||
<div class="relative bg-gradient-to-br from-gray-800/60 to-gray-900/60 border border-gray-700/50 rounded-2xl p-12 shadow-xl backdrop-blur-sm">
|
||||
<div class="relative text-center max-w-4xl mx-auto">
|
||||
<div class="inline-flex items-center justify-center w-20 h-20 rounded-full bg-gradient-to-br from-yellow-500/20 to-orange-500/20 mb-6">
|
||||
<i class="text-yellow-400 text-3xl fas fa-robot"></i>
|
||||
</div>
|
||||
<h2 class="text-3xl md:text-4xl font-bold text-gray-100 mb-6">No backends installed yet</h2>
|
||||
<p class="text-xl text-gray-300 mb-8 leading-relaxed">Get started by installing backends from the gallery or check our documentation for guidance</p>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<!-- Backends -->
|
||||
{{ range .InstalledBackends }}
|
||||
<div class="group relative bg-gradient-to-br from-gray-800/60 to-gray-900/60 border border-gray-700/50 rounded-2xl overflow-hidden transition-all duration-500 hover:shadow-xl hover:shadow-yellow-500/5 hover:-translate-y-1 hover:border-yellow-500/30">
|
||||
<div class="p-6">
|
||||
<div class="flex items-start space-x-4">
|
||||
<div class="w-16 h-16 rounded-xl overflow-hidden flex-shrink-0 bg-gradient-to-br from-gray-700/50 to-gray-800/50 flex items-center justify-center">
|
||||
<i class="fas fa-cog text-2xl text-gray-400"></i>
|
||||
{{ if eq (len .InstalledBackends) 0 }}
|
||||
<!-- No backends state -->
|
||||
<div class="relative bg-[#1E293B]/80 border border-[#8B5CF6]/20 rounded-2xl p-12 shadow-xl backdrop-blur-sm">
|
||||
<div class="absolute inset-0 rounded-2xl bg-gradient-to-br from-purple-500/5 to-cyan-500/5"></div>
|
||||
<div class="relative text-center max-w-4xl mx-auto">
|
||||
<div class="inline-flex items-center justify-center w-20 h-20 rounded-full bg-[#8B5CF6]/10 border border-[#8B5CF6]/20 mb-6">
|
||||
<i class="text-[#8B5CF6] text-3xl fas fa-cogs"></i>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="font-bold text-xl text-white truncate mb-2">{{.Name}}</h3>
|
||||
<h2 class="text-3xl md:text-4xl font-bold text-[#E5E7EB] mb-6">No backends installed yet</h2>
|
||||
<p class="text-xl text-[#94A3B8] mb-8 leading-relaxed">Backends power your AI models. Install them from the backend gallery to get started</p>
|
||||
|
||||
<div class="flex flex-wrap justify-center gap-4">
|
||||
<a href="/browse/backends" class="inline-flex items-center bg-[#8B5CF6] hover:bg-[#8B5CF6]/90 text-white py-3 px-6 rounded-xl font-semibold transition-all duration-300 transform hover:scale-105 hover:shadow-[0_0_20px_rgba(139,92,246,0.4)]">
|
||||
<i class="fas fa-cogs mr-2"></i>
|
||||
Browse Backend Gallery
|
||||
</a>
|
||||
<a href="https://localai.io/backends/" target="_blank" class="inline-flex items-center bg-[#1E293B] hover:bg-[#1E293B]/80 border border-[#8B5CF6]/20 text-[#E5E7EB] py-3 px-6 rounded-xl font-semibold transition-all duration-300 transform hover:scale-105">
|
||||
<i class="fas fa-book mr-2"></i>
|
||||
Documentation
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ else }}
|
||||
<!-- Backends Grid -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
|
||||
{{ range .InstalledBackends }}
|
||||
<div class="group relative bg-[#1E293B] border border-[#8B5CF6]/20 rounded-2xl overflow-hidden transition-all duration-500 hover:shadow-[0_0_20px_rgba(139,92,246,0.2)] hover:-translate-y-2 hover:border-[#8B5CF6]/50">
|
||||
<!-- Card Header -->
|
||||
<div class="relative p-6 border-b border-[#101827]">
|
||||
<div class="flex items-start space-x-4">
|
||||
<div class="w-16 h-16 rounded-xl overflow-hidden flex-shrink-0 bg-[#101827] flex items-center justify-center group-hover:scale-110 transition-transform duration-300">
|
||||
<i class="fas fa-cog text-2xl text-[#8B5CF6]"></i>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="font-bold text-xl text-[#E5E7EB] truncate mb-2 group-hover:text-[#8B5CF6] transition-colors">{{.Name}}</h3>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{{ if .IsSystem }}
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-blue-500/10 text-blue-300 border border-blue-500/30">
|
||||
<i class="fas fa-shield-alt mr-1"></i>System
|
||||
</span>
|
||||
{{ else }}
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-green-500/10 text-green-300 border border-green-500/30">
|
||||
<i class="fas fa-download mr-1"></i>User Installed
|
||||
</span>
|
||||
{{ end }}
|
||||
|
||||
{{ if .IsMeta }}
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-[#8B5CF6]/20 text-[#8B5CF6] border border-[#8B5CF6]/30">
|
||||
<i class="fas fa-layer-group mr-1"></i>Meta
|
||||
</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Backend Details -->
|
||||
<div class="p-6">
|
||||
<div class="space-y-3 text-sm">
|
||||
{{ if and .Metadata .Metadata.Alias }}
|
||||
<div class="flex items-start">
|
||||
<i class="fas fa-tag text-[#94A3B8] mr-2 mt-0.5"></i>
|
||||
<div class="flex-1">
|
||||
<span class="text-[#94A3B8]">Alias:</span>
|
||||
<span class="text-[#E5E7EB] ml-1">{{.Metadata.Alias}}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ if and .Metadata .Metadata.InstalledAt }}
|
||||
<div class="flex items-start">
|
||||
<i class="fas fa-calendar text-[#94A3B8] mr-2 mt-0.5"></i>
|
||||
<div class="flex-1">
|
||||
<span class="text-[#94A3B8]">Installed:</span>
|
||||
<span class="text-[#E5E7EB] ml-1">{{.Metadata.InstalledAt}}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ if and .Metadata .Metadata.MetaBackendFor }}
|
||||
<div class="flex items-start">
|
||||
<i class="fas fa-link text-[#94A3B8] mr-2 mt-0.5"></i>
|
||||
<div class="flex-1">
|
||||
<span class="text-[#94A3B8]">Meta backend for:</span>
|
||||
<span class="text-[#8B5CF6] ml-1 font-semibold">{{.Metadata.MetaBackendFor}}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ if and .Metadata .Metadata.GalleryURL }}
|
||||
<div class="flex items-start">
|
||||
<i class="fas fa-globe text-[#94A3B8] mr-2 mt-0.5"></i>
|
||||
<div class="flex-1">
|
||||
<span class="text-[#94A3B8]">Gallery:</span>
|
||||
<a href="{{.Metadata.GalleryURL}}" target="_blank" class="text-[#38BDF8] hover:text-[#38BDF8]/80 ml-1 truncate inline-block max-w-[200px] align-bottom">
|
||||
{{.Metadata.GalleryURL}}
|
||||
<i class="fas fa-external-link-alt text-xs ml-1"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<div class="flex items-start">
|
||||
<i class="fas fa-folder text-[#94A3B8] mr-2 mt-0.5"></i>
|
||||
<div class="flex-1">
|
||||
<span class="text-[#94A3B8]">Path:</span>
|
||||
<span class="text-[#E5E7EB] ml-1 text-xs font-mono truncate block">{{.RunFile}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
{{ if not .IsSystem }}
|
||||
<div class="flex justify-end items-center pt-4 mt-4 border-t border-[#101827]">
|
||||
<button
|
||||
@click="deleteBackend('{{.Name}}')"
|
||||
class="group/delete inline-flex items-center text-sm font-semibold text-red-400 hover:text-red-300 hover:bg-red-500/10 rounded-lg px-3 py-2 transition-all duration-200">
|
||||
<i class="fas fa-trash-alt mr-2 group-hover/delete:animate-bounce"></i>Delete
|
||||
</button>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{template "views/partials/footer" .}}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Alpine.js component for index dashboard
|
||||
function indexDashboard() {
|
||||
return {
|
||||
notifications: [],
|
||||
|
||||
init() {
|
||||
// Initialize component
|
||||
},
|
||||
|
||||
addNotification(message, type = 'success') {
|
||||
const id = Date.now();
|
||||
this.notifications.push({ id, message, type });
|
||||
// Auto-dismiss after 5 seconds
|
||||
setTimeout(() => this.dismissNotification(id), 5000);
|
||||
},
|
||||
|
||||
dismissNotification(id) {
|
||||
this.notifications = this.notifications.filter(n => n.id !== id);
|
||||
},
|
||||
|
||||
async deleteBackend(backendName) {
|
||||
if (!confirm(`Are you sure you want to delete the backend "${backendName}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/backends/system/delete/${encodeURIComponent(backendName)}`, {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.success) {
|
||||
this.addNotification(`Backend "${backendName}" deleted successfully!`, 'success');
|
||||
// Reload page after short delay
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1500);
|
||||
} else {
|
||||
this.addNotification(`Failed to delete backend: ${data.error || 'Unknown error'}`, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting backend:', error);
|
||||
this.addNotification(`Failed to delete backend: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleStopModel(modelName) {
|
||||
if (!confirm('Are you sure you wish to stop this model?')) {
|
||||
return;
|
||||
@@ -428,4 +599,4 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user