Files
LocalAI/core/http/views/backends.html
Ettore Di Giacinto bef4c10629 feat(ui): General improvements (#6072)
* wip

* Simplify stop

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* Improve UI

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* Show installed backends at the index

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* Imporve UI

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

---------

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2025-08-16 07:44:50 +02:00

335 lines
17 KiB
HTML

<!DOCTYPE html>
<html lang="en">
{{template "views/partials/head" .}}
<body class="bg-gradient-to-br from-gray-900 via-gray-950 to-black text-gray-200">
<div class="flex flex-col min-h-screen">
{{template "views/partials/navbar" .}}
{{ $numBackendsPerPage := 21 }}
<div class="container mx-auto px-4 py-8 flex-grow">
<!-- Hero Header -->
<div class="relative bg-gradient-to-r from-emerald-900/40 via-teal-900/30 to-cyan-900/40 rounded-3xl shadow-2xl p-8 mb-12 overflow-hidden">
<!-- Background Pattern -->
<div class="absolute inset-0 opacity-10">
<div class="absolute inset-0 bg-gradient-to-r from-emerald-500/20 to-cyan-500/20"></div>
<div class="absolute top-0 left-0 w-full h-full" style="background-image: radial-gradient(circle at 1px 1px, rgba(255,255,255,0.15) 1px, transparent 0); background-size: 20px 20px;"></div>
</div>
<div class="relative max-w-5xl mx-auto text-center">
<h1 class="text-4xl md:text-5xl font-bold text-white mb-4">
<span class="bg-clip-text text-transparent bg-gradient-to-r from-emerald-400 via-teal-400 to-cyan-400">
Backend Management
</span>
</h1>
<p class="text-lg md:text-xl text-gray-300 mb-6 font-light">
Discover and install AI backends to power your models
</p>
<div class="flex flex-wrap justify-center items-center gap-6 text-sm md:text-base">
<div class="flex items-center bg-white/10 rounded-full px-4 py-2">
<div class="w-2 h-2 bg-emerald-400 rounded-full mr-2 animate-pulse"></div>
<span class="font-semibold text-emerald-300">{{.AvailableBackends}}</span>
<span class="text-gray-300 ml-1">backends available</span>
</div>
<a href="https://localai.io/backends/" target="_blank"
class="flex items-center bg-cyan-600/80 hover:bg-cyan-600 text-white px-4 py-2 rounded-full transition-all duration-300 hover:scale-105">
<i class="fas fa-info-circle mr-2"></i>
<span>Documentation</span>
<i class="fas fa-external-link-alt ml-2 text-xs"></i>
</a>
</div>
</div>
</div>
{{template "views/partials/inprogress" .}}
<!-- Search and Filter Section -->
<div class="relative bg-gradient-to-br from-gray-800/80 to-gray-900/80 rounded-2xl p-8 mb-8 shadow-xl border border-gray-700/50 backdrop-blur-sm">
<div class="absolute inset-0 rounded-2xl bg-gradient-to-br from-emerald-500/5 to-cyan-500/5"></div>
<div class="relative">
<!-- Search Input -->
<div class="mb-8">
<h3 class="text-xl font-semibold text-white mb-4 flex items-center">
<i class="fas fa-search mr-3 text-emerald-400"></i>
Find Backend Components
</h3>
<div class="relative">
<div class="absolute inset-y-0 start-0 flex items-center ps-4 pointer-events-none">
<i class="fas fa-search text-gray-400"></i>
</div>
<input class="w-full pl-12 pr-16 py-4 text-base font-normal text-gray-300 bg-gray-900/90 border border-gray-700/70 rounded-xl transition-all duration-300 focus:text-gray-200 focus:bg-gray-900 focus:border-emerald-500 focus:ring-2 focus:ring-emerald-500/50 focus:outline-none"
type="search"
name="search"
placeholder="Search backends by name, description or type..."
hx-post="browse/search/backends"
hx-trigger="input changed delay:500ms, search"
hx-target="#search-results"
oninput="hidePagination()"
onchange="hidePagination()"
onsearch="hidePagination()"
hx-indicator=".htmx-indicator">
<span class="htmx-indicator absolute right-4 top-4">
<svg class="animate-spin h-6 w-6 text-emerald-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</span>
</div>
</div>
<!-- Filter by Type -->
<div>
<h3 class="text-lg font-semibold text-white mb-4 flex items-center">
<i class="fas fa-filter mr-3 text-teal-400"></i>
Filter by Backend Type
</h3>
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-3">
<button hx-post="browse/search/backends"
class="group flex items-center justify-center rounded-xl px-4 py-3 text-sm font-semibold bg-gradient-to-r from-indigo-600/80 to-indigo-700/80 hover:from-indigo-600 hover:to-indigo-700 text-indigo-100 border border-indigo-500/30 hover:border-indigo-400/50 transition-all duration-300 transform hover:scale-105 hover:shadow-lg hover:shadow-indigo-500/25"
hx-target="#search-results"
hx-vals='{"search": "llm"}'
onclick="hidePagination()"
hx-indicator=".htmx-indicator">
<i class="fas fa-brain mr-2 group-hover:animate-pulse"></i>
<span>LLM</span>
</button>
<button hx-post="browse/search/backends"
class="group flex items-center justify-center rounded-xl px-4 py-3 text-sm font-semibold bg-gradient-to-r from-purple-600/80 to-purple-700/80 hover:from-purple-600 hover:to-purple-700 text-purple-100 border border-purple-500/30 hover:border-purple-400/50 transition-all duration-300 transform hover:scale-105 hover:shadow-lg hover:shadow-purple-500/25"
hx-target="#search-results"
hx-vals='{"search": "diffusion"}'
onclick="hidePagination()"
hx-indicator=".htmx-indicator">
<i class="fas fa-image mr-2 group-hover:animate-pulse"></i>
<span>Diffusion</span>
</button>
<button hx-post="browse/search/backends"
class="group flex items-center justify-center rounded-xl px-4 py-3 text-sm font-semibold bg-gradient-to-r from-blue-600/80 to-blue-700/80 hover:from-blue-600 hover:to-blue-700 text-blue-100 border border-blue-500/30 hover:border-blue-400/50 transition-all duration-300 transform hover:scale-105 hover:shadow-lg hover:shadow-blue-500/25"
hx-target="#search-results"
hx-vals='{"search": "tts"}'
onclick="hidePagination()"
hx-indicator=".htmx-indicator">
<i class="fas fa-microphone mr-2 group-hover:animate-pulse"></i>
<span>TTS</span>
</button>
<button hx-post="browse/search/backends"
class="group flex items-center justify-center rounded-xl px-4 py-3 text-sm font-semibold bg-gradient-to-r from-green-600/80 to-green-700/80 hover:from-green-600 hover:to-green-700 text-green-100 border border-green-500/30 hover:border-green-400/50 transition-all duration-300 transform hover:scale-105 hover:shadow-lg hover:shadow-green-500/25"
hx-target="#search-results"
hx-vals='{"search": "whisper"}'
onclick="hidePagination()"
hx-indicator=".htmx-indicator">
<i class="fas fa-headphones mr-2 group-hover:animate-pulse"></i>
<span>Whisper</span>
</button>
<button hx-post="browse/search/backends"
class="group flex items-center justify-center rounded-xl px-4 py-3 text-sm font-semibold bg-gradient-to-r from-red-600/80 to-red-700/80 hover:from-red-600 hover:to-red-700 text-red-100 border border-red-500/30 hover:border-red-400/50 transition-all duration-300 transform hover:scale-105 hover:shadow-lg hover:shadow-red-500/25"
hx-target="#search-results"
hx-vals='{"search": "object-detection"}'
onclick="hidePagination()"
hx-indicator=".htmx-indicator">
<i class="fas fa-eye mr-2 group-hover:animate-pulse"></i>
<span>Vision</span>
</button>
</div>
</div>
</div>
</div>
<!-- Results Section -->
<div id="search-results" class="transition-all duration-300">
{{.Backends}}
</div>
<!-- Pagination -->
{{ if gt .AvailableBackends $numBackendsPerPage }}
<div id="paginate" class="flex justify-center mt-12">
<div class="flex items-center gap-4 bg-gray-800/60 rounded-2xl p-4 backdrop-blur-sm border border-gray-700/50">
{{ if .PrevPage }}
<button onclick="window.location.href='browse/backends?page={{.PrevPage}}'"
class="group flex items-center justify-center h-12 w-12 bg-gray-700/80 hover:bg-emerald-600 text-gray-300 hover:text-white rounded-xl shadow-lg transition-all duration-300 ease-in-out transform hover:scale-110">
<i class="fas fa-chevron-left group-hover:animate-pulse"></i>
</button>
{{ end }}
<div class="text-gray-300 text-sm font-medium px-4">
<span class="text-gray-400">Page</span>
<span class="text-white font-bold text-lg mx-2">{{.CurrentPage}}</span>
<span class="text-gray-400">of</span>
<span class="text-white font-bold text-lg mx-2">{{.TotalPages}}</span>
</div>
{{ if .NextPage }}
<button onclick="window.location.href='browse/backends?page={{.NextPage}}'"
class="group flex items-center justify-center h-12 w-12 bg-gray-700/80 hover:bg-emerald-600 text-gray-300 hover:text-white rounded-xl shadow-lg transition-all duration-300 ease-in-out transform hover:scale-110">
<i class="fas fa-chevron-right group-hover:animate-pulse"></i>
</button>
{{ end }}
</div>
</div>
{{ end }}
</div>
{{template "views/partials/footer" .}}
</div>
<style>
/* Enhanced scrollbar styling */
.scrollbar-thin::-webkit-scrollbar {
width: 6px;
}
.scrollbar-thin::-webkit-scrollbar-track {
background: rgba(31, 41, 55, 0.5);
border-radius: 6px;
}
.scrollbar-thin::-webkit-scrollbar-thumb {
background: rgba(107, 114, 128, 0.5);
border-radius: 6px;
}
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
background: rgba(107, 114, 128, 0.8);
}
/* Add some custom CSS for backend cards to match our theme */
#search-results .dark\:bg-gray-800 {
background: linear-gradient(135deg, rgba(31, 41, 55, 0.9) 0%, rgba(17, 24, 39, 0.9) 100%) !important;
border: 1px solid rgba(75, 85, 99, 0.5) !important;
border-radius: 1rem !important;
transition: all 0.5s ease !important;
backdrop-filter: blur(8px) !important;
}
#search-results .dark\:bg-gray-800:hover {
transform: translateY(-8px) !important;
box-shadow: 0 25px 50px -12px rgba(16, 185, 129, 0.1) !important;
border-color: rgba(16, 185, 129, 0.3) !important;
}
/* Style the install buttons */
#search-results .bg-blue-600 {
background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important;
border-radius: 0.75rem !important;
padding: 0.75rem 1.5rem !important;
font-weight: 600 !important;
transition: all 0.3s ease !important;
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.25) !important;
}
#search-results .bg-blue-600:hover {
background: linear-gradient(135deg, #059669 0%, #047857 100%) !important;
transform: scale(1.05) !important;
box-shadow: 0 8px 25px rgba(16, 185, 129, 0.4) !important;
}
/* Style the reinstall buttons specifically */
#search-results .bg-primary {
background: linear-gradient(135deg, #06b6d4 0%, #0891b2 100%) !important;
border-radius: 0.75rem !important;
padding: 0.75rem 1.5rem !important;
font-weight: 600 !important;
transition: all 0.3s ease !important;
box-shadow: 0 4px 15px rgba(6, 182, 212, 0.25) !important;
}
#search-results .bg-primary:hover {
background: linear-gradient(135deg, #0891b2 0%, #0e7490 100%) !important;
transform: scale(1.05) !important;
box-shadow: 0 8px 25px rgba(6, 182, 212, 0.4) !important;
}
/* Style the delete buttons */
#search-results .bg-red-800 {
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%) !important;
border-radius: 0.75rem !important;
padding: 0.75rem 1.5rem !important;
font-weight: 600 !important;
transition: all 0.3s ease !important;
box-shadow: 0 4px 15px rgba(220, 38, 38, 0.25) !important;
}
#search-results .bg-red-800:hover {
background: linear-gradient(135deg, #b91c1c 0%, #991b1b 100%) !important;
transform: scale(1.05) !important;
box-shadow: 0 8px 25px rgba(220, 38, 38, 0.4) !important;
}
/* Style the info buttons */
#search-results .bg-gray-700 {
background: linear-gradient(135deg, #374151 0%, #1f2937 100%) !important;
border-radius: 0.75rem !important;
padding: 0.75rem 1.5rem !important;
font-weight: 600 !important;
transition: all 0.3s ease !important;
box-shadow: 0 4px 15px rgba(55, 65, 81, 0.25) !important;
}
#search-results .bg-gray-700:hover {
background: linear-gradient(135deg, #1f2937 0%, #111827 100%) !important;
transform: scale(1.05) !important;
box-shadow: 0 8px 25px rgba(55, 65, 81, 0.4) !important;
}
/* Style the backend images */
#search-results img.rounded-t-lg {
border-radius: 1rem !important;
transition: transform 0.3s ease !important;
}
#search-results .dark\:bg-gray-800:hover img.rounded-t-lg {
transform: scale(1.05) !important;
}
/* Style the progress bars */
#search-results .progress {
background: linear-gradient(135deg, rgba(16, 185, 129, 0.2) 0%, rgba(6, 182, 212, 0.2) 100%) !important;
border-radius: 0.5rem !important;
border: 1px solid rgba(16, 185, 129, 0.3) !important;
}
/* Style action buttons */
#search-results button[class*="primary"] {
background: linear-gradient(135deg, #06b6d4 0%, #0891b2 100%) !important;
border-radius: 0.5rem !important;
transition: all 0.2s ease !important;
}
#search-results button[class*="primary"]:hover {
transform: scale(1.05) !important;
box-shadow: 0 4px 15px rgba(6, 182, 212, 0.3) !important;
}
</style>
<script>
function hidePagination() {
const paginateDiv = document.getElementById('paginate');
if (paginateDiv) {
paginateDiv.style.display = 'none';
}
}
// Listen for the htmx:afterSwap event to handle cases when the search results are updated
document.body.addEventListener('htmx:afterSwap', function(event) {
if (event.detail.target.id === 'search-results') {
hidePagination();
}
});
// Add loading state animation
document.body.addEventListener('htmx:beforeRequest', function(event) {
const searchInput = document.querySelector('input[name="search"]');
if (searchInput && event.detail.elt === searchInput) {
searchInput.classList.add('animate-pulse');
}
});
document.body.addEventListener('htmx:afterRequest', function(event) {
const searchInput = document.querySelector('input[name="search"]');
if (searchInput) {
searchInput.classList.remove('animate-pulse');
}
});
</script>
</body>
</html>