feat: restyle index (#7282)

* Move management to separate section

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

* Make index to redirect to chat

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

* Use logo in index

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

* work out the wizard in the front-page

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

---------

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
Ettore Di Giacinto
2025-11-16 11:01:05 +01:00
committed by GitHub
parent d1a0dd10e6
commit cd7d384500
17 changed files with 1138 additions and 812 deletions

View File

@@ -64,8 +64,13 @@ func WelcomeEndpoint(appConfig *config.ApplicationConfig,
// The client expects a JSON response
return c.JSON(200, summary)
} else {
// Render index
return c.Render(200, "views/index", summary)
// Check if this is the manage route
templateName := "views/index"
if strings.HasSuffix(c.Request().URL.Path, "/manage") || c.Request().URL.Path == "/manage" {
templateName = "views/manage"
}
// Render appropriate template
return c.Render(200, templateName, summary)
}
}
}

View File

@@ -21,6 +21,7 @@ func RegisterUIRoutes(app *echo.Echo,
var processingOps = services.NewOpCache(galleryService)
app.GET("/", localai.WelcomeEndpoint(appConfig, cl, ml, processingOps))
app.GET("/manage", localai.WelcomeEndpoint(appConfig, cl, ml, processingOps))
// P2P
app.GET("/p2p/", func(c echo.Context) error {

View File

@@ -1237,3 +1237,55 @@ document.addEventListener("alpine:init", () => {
}
});
// Check for message from index page on load
document.addEventListener('DOMContentLoaded', function() {
// Wait for Alpine to be ready
setTimeout(() => {
const chatData = localStorage.getItem('localai_index_chat_data');
if (chatData) {
try {
const data = JSON.parse(chatData);
const input = document.getElementById('input');
if (input && data.message) {
// Set the message in the input
input.value = data.message;
// Process files if any
if (data.imageFiles && data.imageFiles.length > 0) {
data.imageFiles.forEach(file => {
images.push(file.data);
});
}
if (data.audioFiles && data.audioFiles.length > 0) {
data.audioFiles.forEach(file => {
audios.push(file.data);
});
}
if (data.textFiles && data.textFiles.length > 0) {
data.textFiles.forEach(file => {
fileContents.push({ name: file.name, content: file.data });
currentFileNames.push(file.name);
});
}
// Clear localStorage
localStorage.removeItem('localai_index_chat_data');
// Auto-submit after a short delay to ensure everything is ready
setTimeout(() => {
if (input.value.trim()) {
processAndSendMessage(input.value);
}
}, 500);
}
} catch (error) {
console.error('Error processing chat data from index:', error);
localStorage.removeItem('localai_index_chat_data');
}
}
}, 300);
});

View File

@@ -9,9 +9,9 @@
<div class="container mx-auto px-4 py-8 flex-grow">
<!-- Error Section -->
<div class="bg-[#1E293B] border border-[#38BDF8]/20 rounded-2xl shadow-2xl shadow-[#38BDF8]/10 p-8 mb-10">
<div class="bg-[#1E293B] border border-[#38BDF8]/20 rounded-xl p-8 mb-10">
<div class="max-w-4xl mx-auto text-center">
<div class="mb-6 text-6xl text-[#38BDF8] animate-pulse">
<div class="mb-6 text-6xl text-[#38BDF8]">
<i class="fas fa-exclamation-circle"></i>
</div>
<h1 class="text-4xl md:text-5xl font-bold text-[#E5E7EB] mb-4">
@@ -22,23 +22,21 @@
<p class="text-xl text-[#94A3B8] mb-6">The page you're looking for doesn't exist or has been moved</p>
<div class="flex flex-wrap justify-center gap-4">
<a href="./"
class="group flex items-center bg-[#38BDF8] hover:bg-[#38BDF8]/90 text-[#101827] font-semibold py-3 px-6 rounded-lg transition duration-300 ease-in-out transform hover:scale-105 hover:shadow-[0_0_20px_rgba(56,189,248,0.4)]">
class="inline-flex items-center bg-[#38BDF8] hover:bg-[#38BDF8]/90 text-[#101827] font-semibold py-3 px-6 rounded-lg transition-colors">
<i class="fas fa-home mr-2"></i>
<span>Return Home</span>
<i class="fas fa-arrow-right opacity-0 group-hover:opacity-100 group-hover:translate-x-2 ml-2 transition-all duration-300"></i>
</a>
<a href="browse"
class="group flex items-center bg-[#8B5CF6] hover:bg-[#8B5CF6]/90 text-white font-semibold py-3 px-6 rounded-lg transition duration-300 ease-in-out transform hover:scale-105 hover:shadow-[0_0_20px_rgba(139,92,246,0.4)]">
class="inline-flex items-center bg-[#8B5CF6] hover:bg-[#8B5CF6]/90 text-white font-semibold py-3 px-6 rounded-lg transition-colors">
<i class="fas fa-images mr-2"></i>
<span>Browse Gallery</span>
<i class="fas fa-arrow-right opacity-0 group-hover:opacity-100 group-hover:translate-x-2 ml-2 transition-all duration-300"></i>
</a>
</div>
</div>
</div>
<!-- Additional Information -->
<div class="bg-[#1E293B]/80 border border-[#1E293B] rounded-xl p-8 shadow-lg backdrop-blur-sm">
<div class="bg-[#1E293B] border border-[#1E293B] rounded-xl p-8">
<div class="text-center max-w-3xl mx-auto">
<div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-yellow-500/10 border border-yellow-500/20 mb-4">
<i class="text-yellow-400 text-2xl fa-solid fa-triangle-exclamation"></i>

View File

@@ -11,21 +11,21 @@
<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"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100"
x-transition:leave-end="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">
class="rounded-lg 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">
<button @click="dismissNotification(notification.id)" class="flex-shrink-0 text-white hover:opacity-80 transition-opacity">
<i class="fas fa-times"></i>
</button>
</div>
@@ -35,14 +35,8 @@
<div class="container mx-auto px-4 py-8 flex-grow">
<!-- Hero Header -->
<div class="relative bg-[#1E293B] border border-[#8B5CF6]/20 rounded-3xl shadow-2xl shadow-[#8B5CF6]/10 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-[#8B5CF6]/20 to-[#38BDF8]/20"></div>
<div class="absolute top-0 left-0 w-full h-full" style="background-image: radial-gradient(circle at 1px 1px, rgba(139,92,246,0.15) 1px, transparent 0); background-size: 20px 20px;"></div>
</div>
<div class="relative max-w-5xl mx-auto text-center">
<div class="bg-[#1E293B] border border-[#8B5CF6]/20 rounded-xl p-8 mb-12">
<div class="max-w-5xl mx-auto text-center">
<h1 class="text-4xl md:text-5xl font-bold text-[#E5E7EB] mb-4">
<span class="bg-clip-text text-transparent bg-gradient-to-r from-[#8B5CF6] via-[#38BDF8] to-[#8B5CF6]">
Backend Management
@@ -52,13 +46,13 @@
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>
<div class="flex items-center bg-[#101827] rounded-lg px-4 py-2">
<div class="w-2 h-2 bg-emerald-400 rounded-full mr-2"></div>
<span class="font-semibold text-emerald-300" x-text="availableBackends"></span>
<span class="text-gray-300 ml-1">backends available</span>
<span class="text-[#94A3B8] 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">
class="inline-flex items-center bg-cyan-600 hover:bg-cyan-700 text-white px-4 py-2 rounded-lg transition-colors">
<i class="fas fa-info-circle mr-2"></i>
<span>Documentation</span>
<i class="fas fa-external-link-alt ml-2 text-xs"></i>
@@ -70,28 +64,26 @@
{{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">
<div class="bg-[#1E293B] border border-[#8B5CF6]/20 rounded-xl p-8 mb-8">
<div>
<!-- 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>
<h3 class="text-xl font-semibold text-[#E5E7EB] mb-4 flex items-center">
<i class="fas fa-search mr-3 text-[#8B5CF6]"></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>
<i class="fas fa-search text-[#94A3B8]"></i>
</div>
<input
x-model="searchTerm"
@input.debounce.500ms="fetchBackends()"
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"
class="w-full pl-12 pr-16 py-4 text-base font-normal text-[#E5E7EB] bg-[#101827] border border-[#1E293B] rounded-lg transition-colors focus:text-[#E5E7EB] focus:bg-[#101827] focus:border-[#8B5CF6] focus:ring-2 focus:ring-[#8B5CF6]/50 focus:outline-none"
type="search"
placeholder="Search backends by name, description or type...">
<span class="absolute right-4 top-4" x-show="loading">
<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">
<svg class="animate-spin h-6 w-6 text-[#8B5CF6]" 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>
@@ -107,28 +99,28 @@
</h3>
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-3">
<button @click="filterByTerm('llm')"
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">
<i class="fas fa-brain mr-2 group-hover:animate-pulse"></i>
class="flex items-center justify-center rounded-lg px-4 py-3 text-sm font-semibold bg-indigo-600/20 hover:bg-indigo-600/30 text-indigo-300 border border-indigo-500/30 transition-colors">
<i class="fas fa-brain mr-2"></i>
<span>LLM</span>
</button>
<button @click="filterByTerm('diffusion')"
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">
<i class="fas fa-image mr-2 group-hover:animate-pulse"></i>
class="flex items-center justify-center rounded-lg px-4 py-3 text-sm font-semibold bg-purple-600/20 hover:bg-purple-600/30 text-purple-300 border border-purple-500/30 transition-colors">
<i class="fas fa-image mr-2"></i>
<span>Diffusion</span>
</button>
<button @click="filterByTerm('tts')"
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">
<i class="fas fa-microphone mr-2 group-hover:animate-pulse"></i>
class="flex items-center justify-center rounded-lg px-4 py-3 text-sm font-semibold bg-blue-600/20 hover:bg-blue-600/30 text-blue-300 border border-blue-500/30 transition-colors">
<i class="fas fa-microphone mr-2"></i>
<span>TTS</span>
</button>
<button @click="filterByTerm('whisper')"
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">
<i class="fas fa-headphones mr-2 group-hover:animate-pulse"></i>
class="flex items-center justify-center rounded-lg px-4 py-3 text-sm font-semibold bg-green-600/20 hover:bg-green-600/30 text-green-300 border border-green-500/30 transition-colors">
<i class="fas fa-headphones mr-2"></i>
<span>Whisper</span>
</button>
<button @click="filterByTerm('object-detection')"
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">
<i class="fas fa-eye mr-2 group-hover:animate-pulse"></i>
class="flex items-center justify-center rounded-lg px-4 py-3 text-sm font-semibold bg-red-600/20 hover:bg-red-600/30 text-red-300 border border-red-500/30 transition-colors">
<i class="fas fa-eye mr-2"></i>
<span>Vision</span>
</button>
</div>
@@ -361,8 +353,8 @@
<button @click="goToPage(currentPage - 1)"
:disabled="currentPage <= 1"
:class="currentPage <= 1 ? 'opacity-50 cursor-not-allowed' : ''"
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>
class="flex items-center justify-center h-12 w-12 bg-[#1E293B] hover:bg-emerald-600 text-[#94A3B8] hover:text-white rounded-lg transition-colors">
<i class="fas fa-chevron-left"></i>
</button>
<div class="text-gray-300 text-sm font-medium px-4">
<span class="text-gray-400">Page</span>

View File

@@ -9,9 +9,9 @@
<div class="container mx-auto px-4 py-8 flex-grow">
<!-- Error Section -->
<div class="bg-[#1E293B] border border-red-500/20 rounded-2xl shadow-2xl shadow-red-500/10 p-8 mb-10">
<div class="bg-[#1E293B] border border-red-500/20 rounded-xl p-8 mb-10">
<div class="max-w-4xl mx-auto text-center">
<div class="mb-6 text-6xl text-red-400 animate-pulse">
<div class="mb-6 text-6xl text-red-400">
<i class="fas fa-exclamation-circle"></i>
</div>
<h1 class="text-4xl md:text-5xl font-bold text-[#E5E7EB] mb-4">
@@ -22,23 +22,21 @@
<p class="text-xl text-[#94A3B8] mb-6">{{if .ErrorMessage}}{{.ErrorMessage}}{{else}}An unexpected error occurred{{end}}</p>
<div class="flex flex-wrap justify-center gap-4">
<a href="./"
class="group flex items-center bg-[#38BDF8] hover:bg-[#38BDF8]/90 text-[#101827] font-semibold py-3 px-6 rounded-lg transition duration-300 ease-in-out transform hover:scale-105 hover:shadow-[0_0_20px_rgba(56,189,248,0.4)]">
class="inline-flex items-center bg-[#38BDF8] hover:bg-[#38BDF8]/90 text-[#101827] font-semibold py-3 px-6 rounded-lg transition-colors">
<i class="fas fa-home mr-2"></i>
<span>Return Home</span>
<i class="fas fa-arrow-right opacity-0 group-hover:opacity-100 group-hover:translate-x-2 ml-2 transition-all duration-300"></i>
</a>
<a href="browse"
class="group flex items-center bg-[#8B5CF6] hover:bg-[#8B5CF6]/90 text-white font-semibold py-3 px-6 rounded-lg transition duration-300 ease-in-out transform hover:scale-105 hover:shadow-[0_0_20px_rgba(139,92,246,0.4)]">
class="inline-flex items-center bg-[#8B5CF6] hover:bg-[#8B5CF6]/90 text-white font-semibold py-3 px-6 rounded-lg transition-colors">
<i class="fas fa-images mr-2"></i>
<span>Browse Gallery</span>
<i class="fas fa-arrow-right opacity-0 group-hover:opacity-100 group-hover:translate-x-2 ml-2 transition-all duration-300"></i>
</a>
</div>
</div>
</div>
<!-- Additional Information -->
<div class="bg-[#1E293B]/80 border border-[#1E293B] rounded-xl p-8 shadow-lg backdrop-blur-sm">
<div class="bg-[#1E293B] border border-[#1E293B] rounded-xl p-8">
<div class="text-center max-w-3xl mx-auto">
<div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-yellow-500/10 border border-yellow-500/20 mb-4">
<i class="text-yellow-400 text-2xl fa-solid fa-triangle-exclamation"></i>

View File

@@ -23,12 +23,10 @@
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease, box-shadow 0.3s ease;
transition: background-color 0.2s ease;
}
.network-card:hover {
transform: translateY(-5px);
box-shadow: 0 6px 10px rgba(0, 0, 0, 0.15);
background-color: #374151;
}
.network-title {
font-size: 24px;

View File

@@ -3,438 +3,239 @@
{{template "views/partials/head" .}}
<body class="bg-[#101827] text-[#E5E7EB]">
<div class="flex flex-col min-h-screen" x-data="indexDashboard()">
<div class="flex flex-col min-h-screen">
{{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">
<!-- Background Pattern -->
<div class="absolute inset-0 opacity-10">
<div class="absolute inset-0 bg-gradient-to-r from-[#38BDF8]/20 to-[#8B5CF6]/20"></div>
<div class="absolute top-0 left-0 w-full h-full" style="background-image: radial-gradient(circle at 1px 1px, rgba(56,189,248,0.15) 1px, transparent 0); background-size: 20px 20px;"></div>
</div>
<div class="relative max-w-5xl mx-auto text-center">
<h1 class="text-5xl md:text-6xl font-bold text-[#E5E7EB] mb-6">
<span class="bg-clip-text text-transparent bg-gradient-to-r from-[#38BDF8] via-[#8B5CF6] to-[#38BDF8]">
Welcome to <em class="not-italic font-black">your</em> LocalAI
</span>
</h1>
<p class="text-xl md:text-2xl text-[#94A3B8] mb-8 font-light">The powerful FOSS alternative to OpenAI, Claude, and more</p>
<div class="flex flex-wrap justify-center gap-4">
<a href="https://localai.io" target="_blank"
class="group relative inline-flex items-center bg-[#38BDF8] hover:bg-[#38BDF8]/90 text-[#101827] py-3 px-8 rounded-xl font-semibold transition-all duration-300 ease-in-out transform hover:scale-105 hover:shadow-[0_0_20px_rgba(56,189,248,0.4)]">
<i class="fas fa-book-reader mr-3 text-lg"></i>
<span>Documentation</span>
<i class="fas fa-external-link-alt ml-3 text-sm opacity-70 group-hover:opacity-100 transition-opacity"></i>
</a>
<a href="browse"
class="group relative inline-flex items-center bg-[#8B5CF6] hover:bg-[#8B5CF6]/90 text-white py-3 px-8 rounded-xl font-semibold transition-all duration-300 ease-in-out transform hover:scale-105 hover:shadow-[0_0_20px_rgba(139,92,246,0.4)]">
<i class="fas fa-images mr-3 text-lg"></i>
<span>Model Gallery</span>
<i class="fas fa-arrow-right ml-3 opacity-0 group-hover:opacity-100 group-hover:translate-x-1 transition-all duration-300"></i>
</a>
<a href="/import-model"
class="group relative inline-flex items-center bg-green-600 hover:bg-green-700 text-white py-3 px-8 rounded-xl font-semibold transition-all duration-300 ease-in-out transform hover:scale-105 hover:shadow-[0_0_20px_rgba(34,197,94,0.4)]">
<i class="fas fa-plus mr-3 text-lg"></i>
<span>Import Model</span>
<i class="fas fa-upload ml-3 opacity-70 group-hover:opacity-100 transition-opacity"></i>
</a>
<button id="reload-models-btn"
class="group relative inline-flex items-center bg-orange-600 hover:bg-orange-700 text-white py-3 px-8 rounded-xl font-semibold transition-all duration-300 ease-in-out transform hover:scale-105 hover:shadow-[0_0_20px_rgba(234,88,12,0.4)]">
<i class="fas fa-sync-alt mr-3 text-lg"></i>
<span>Update Models</span>
<i class="fas fa-refresh ml-3 opacity-70 group-hover:opacity-100 transition-opacity"></i>
</button>
</div>
</div>
</div>
<!-- Models Section -->
<div class="models mt-8">
{{template "views/partials/inprogress" .}}
<!-- Main Content - ChatGPT-style minimal interface -->
<div class="flex-1 flex flex-col items-center justify-center px-4 py-12">
<div class="w-full max-w-3xl mx-auto">
{{ if eq (len .ModelsConfig) 0 }}
<!-- No Models State -->
<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">
<i class="text-yellow-400 text-3xl fas fa-robot"></i>
<!-- No Models - Wizard Guide -->
<div class="bg-[#1E293B] border border-[#8B5CF6]/20 rounded-xl p-12">
<div class="text-center max-w-4xl mx-auto">
<div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-[#8B5CF6]/10 border border-[#8B5CF6]/20 mb-6">
<i class="text-[#8B5CF6] text-2xl fas fa-robot"></i>
</div>
<h2 class="text-3xl md:text-4xl font-bold text-[#E5E7EB] mb-6">No models installed yet</h2>
<p class="text-xl text-[#94A3B8] mb-8 leading-relaxed">Get started by installing a model from the gallery or importing it</p>
<h2 class="text-3xl md:text-4xl font-bold text-[#E5E7EB] mb-4">
<span class="bg-clip-text text-transparent bg-gradient-to-r from-[#38BDF8] to-[#8B5CF6]">
No Models Installed
</span>
</h2>
<p class="text-xl text-[#94A3B8] mb-8">
Get started with LocalAI by installing your first model. Choose from our gallery, import your own, or use the API to download models.
</p>
<div class="flex flex-wrap justify-center gap-4 mb-8">
<a href="browse" class="inline-flex items-center bg-[#38BDF8] hover:bg-[#38BDF8]/90 text-[#101827] py-3 px-6 rounded-xl font-semibold transition-all duration-300 transform hover:scale-105 hover:shadow-[0_0_20px_rgba(56,189,248,0.4)]">
<!-- Features Preview -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-10">
<div class="bg-[#101827] border border-[#38BDF8]/20 rounded-lg p-4">
<div class="w-10 h-10 bg-blue-500/10 rounded-lg flex items-center justify-center mx-auto mb-3">
<i class="fas fa-images text-[#38BDF8] text-xl"></i>
</div>
<h3 class="text-sm font-semibold text-[#E5E7EB] mb-2">Model Gallery</h3>
<p class="text-xs text-[#94A3B8]">Browse and install pre-configured models</p>
</div>
<div class="bg-[#101827] border border-[#8B5CF6]/20 rounded-lg p-4">
<div class="w-10 h-10 bg-purple-500/10 rounded-lg flex items-center justify-center mx-auto mb-3">
<i class="fas fa-upload text-[#8B5CF6] text-xl"></i>
</div>
<h3 class="text-sm font-semibold text-[#E5E7EB] mb-2">Import Models</h3>
<p class="text-xs text-[#94A3B8]">Upload your own model files</p>
</div>
<div class="bg-[#101827] border border-green-500/20 rounded-lg p-4">
<div class="w-10 h-10 bg-green-500/10 rounded-lg flex items-center justify-center mx-auto mb-3">
<i class="fas fa-code text-green-400 text-xl"></i>
</div>
<h3 class="text-sm font-semibold text-[#E5E7EB] mb-2">API Download</h3>
<p class="text-xs text-[#94A3B8]">Use the API to download models programmatically</p>
</div>
</div>
<!-- Setup Instructions -->
<div class="bg-[#101827] border border-[#8B5CF6]/20 rounded-xl p-6 mb-8 text-left">
<h3 class="text-lg font-bold text-[#E5E7EB] mb-4 flex items-center">
<i class="fas fa-rocket text-[#8B5CF6] mr-2"></i>
How to Get Started
</h3>
<div class="space-y-4">
<div class="flex items-start">
<div class="flex-shrink-0 w-8 h-8 rounded-full bg-[#8B5CF6]/20 flex items-center justify-center mr-3 mt-0.5">
<span class="text-[#8B5CF6] font-bold text-sm">1</span>
</div>
<div class="flex-1">
<p class="text-[#E5E7EB] font-medium mb-2">Browse the Model Gallery</p>
<p class="text-[#94A3B8] text-sm">Explore our curated collection of pre-configured models. Find models for chat, image generation, audio processing, and more.</p>
</div>
</div>
<div class="flex items-start">
<div class="flex-shrink-0 w-8 h-8 rounded-full bg-[#8B5CF6]/20 flex items-center justify-center mr-3 mt-0.5">
<span class="text-[#8B5CF6] font-bold text-sm">2</span>
</div>
<div class="flex-1">
<p class="text-[#E5E7EB] font-medium mb-2">Install a Model</p>
<p class="text-[#94A3B8] text-sm">Click on a model from the gallery to install it, or use the import feature to upload your own model files.</p>
</div>
</div>
<div class="flex items-start">
<div class="flex-shrink-0 w-8 h-8 rounded-full bg-[#8B5CF6]/20 flex items-center justify-center mr-3 mt-0.5">
<span class="text-[#8B5CF6] font-bold text-sm">3</span>
</div>
<div class="flex-1">
<p class="text-[#E5E7EB] font-medium mb-2">Start Chatting</p>
<p class="text-[#94A3B8] text-sm">Once installed, return to this page to start chatting with your model or use the API to interact programmatically.</p>
</div>
</div>
</div>
</div>
<div class="flex flex-wrap justify-center gap-4">
<a href="/browse/"
class="inline-flex items-center bg-[#8B5CF6] hover:bg-[#8B5CF6]/90 text-white py-3 px-6 rounded-lg font-semibold transition-colors">
<i class="fas fa-images mr-2"></i>
Browse Model Gallery
</a>
<a href="/import-model" class="inline-flex items-center bg-green-600 hover:bg-green-700 text-white py-3 px-6 rounded-xl font-semibold transition-all duration-300 transform hover:scale-105 hover:shadow-[0_0_20px_rgba(34,197,94,0.4)]">
<a href="/import-model"
class="inline-flex items-center bg-[#38BDF8] hover:bg-[#38BDF8]/90 text-white py-3 px-6 rounded-lg font-semibold transition-colors">
<i class="fas fa-upload mr-2"></i>
Import Model
</a>
<a href="https://localai.io/basics/getting_started/" target="_blank" class="inline-flex items-center bg-[#1E293B] hover:bg-[#1E293B]/80 border border-[#38BDF8]/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 href="https://localai.io/basics/getting_started/" target="_blank"
class="inline-flex items-center bg-[#1E293B] hover:bg-[#1E293B]/80 border border-[#8B5CF6]/20 text-[#E5E7EB] py-3 px-6 rounded-lg font-semibold transition-colors">
<i class="fas fa-graduation-cap mr-2"></i>
Getting Started
<i class="fas fa-external-link-alt ml-2 text-sm"></i>
</a>
</div>
{{ if ne (len .Models) 0 }}
<div class="mt-12 pt-8 border-t border-[#38BDF8]/20">
<h3 class="text-2xl font-bold text-[#E5E7EB] mb-6 flex items-center">
<i class="fas fa-file-alt mr-2 text-[#38BDF8]"></i>
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-[#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>
<div class="flex-1">
<p class="font-semibold text-[#E5E7EB] truncate">{{.}}</p>
<p class="text-xs text-[#94A3B8]">No configuration</p>
</div>
</div>
{{end}}
</div>
</div>
{{end}}
</div>
</div>
{{ else }}
<!-- Models Grid -->
{{ $modelsN := len .ModelsConfig}}
{{ $modelsN = add $modelsN (len .Models)}}
<div class="mb-8 flex flex-col md:flex-row md:items-center md:justify-between">
<div class="mb-4 md:mb-0">
<h2 class="text-3xl md:text-4xl font-bold text-[#E5E7EB] mb-2 flex items-center">
<i class="fas fa-brain mr-3 text-[#38BDF8]"></i>
Installed Models
</h2>
<p class="text-[#94A3B8]">
<span class="text-[#38BDF8] font-semibold">{{$modelsN}}</span> model{{if gt $modelsN 1}}s{{end}} ready to use
</p>
<!-- Welcome Message -->
<div class="text-center mb-12">
<div class="mb-6 flex justify-center">
<img src="static/logo.png" alt="LocalAI Logo" class="h-24 md:h-32 drop-shadow-[0_0_15px_rgba(56,189,248,0.3)]">
</div>
<p class="text-lg text-[#94A3B8]">How can I help you today?</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
{{$galleryConfig:=.GalleryConfig}}
{{ $loadedModels := .LoadedModels }}
{{ range .ModelsConfig }}
{{ $backendCfg := . }}
{{ $cfg:= index $galleryConfig .Name}}
<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">
<div class="relative 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">
{{ if and $cfg $cfg.Icon }}
<img src="{{$cfg.Icon}}"
class="w-full h-full object-contain"
alt="{{.Name}} icon"
>
{{ else }}
<i class="fas fa-brain text-2xl text-[#38BDF8]"></i>
{{ end }}
{{ if index $loadedModels .Name }}
<div class="absolute -top-1 -right-1 w-4 h-4 bg-green-500 rounded-full border-2 border-[#1E293B] animate-pulse"></div>
{{ end }}
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<h3 class="font-bold text-xl text-[#E5E7EB] truncate group-hover:text-[#38BDF8] transition-colors">{{.Name}}</h3>
</div>
<div class="mt-2 flex flex-wrap gap-2">
{{ if .Backend }}
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-[#38BDF8]/20 text-[#38BDF8] border border-[#38BDF8]/30">
<i class="fas fa-cog mr-1"></i>{{.Backend}}
</span>
{{ else }}
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-yellow-500/10 text-yellow-300 border border-yellow-500/30">
<i class="fas fa-magic mr-1"></i>Auto
</span>
{{ end }}
{{ if and $backendCfg (or (ne $backendCfg.MCP.Servers "") (ne $backendCfg.MCP.Stdio "")) }}
<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-plug mr-1"></i>MCP
</span>
{{ end }}
{{ if index $loadedModels .Name }}
<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-play mr-1"></i>Running
</span>
{{ end }}
</div>
</div>
</div>
</div>
<!-- Usage Buttons -->
<div class="p-6">
<div class="flex flex-wrap gap-2 mb-4">
{{ range .KnownUsecaseStrings }}
<!-- Chat Input Form -->
<div class="mb-8" x-data="{ selectedModel: '', inputValue: '', shiftPressed: false, fileName: '', imageFiles: [], audioFiles: [], textFiles: [] }">
<!-- Model Selector -->
<div class="mb-4">
<label class="block text-sm font-medium text-[#94A3B8] mb-2">Select Model</label>
<select
x-model="selectedModel"
class="w-full bg-[#1E293B] text-[#E5E7EB] border border-[#38BDF8]/20 focus:border-[#38BDF8] focus:ring-2 focus:ring-[#38BDF8]/50 rounded-lg p-3 appearance-none"
required
>
<option value="" disabled class="text-[#94A3B8]">Select a model to chat with...</option>
{{ range .ModelsConfig }}
{{ $cfg := . }}
{{ range .KnownUsecaseStrings }}
{{ if eq . "FLAG_CHAT" }}
<a href="chat/{{$backendCfg.Name}}" class="flex-1 min-w-0 group/chat inline-flex items-center justify-center rounded-xl px-4 py-3 text-sm font-semibold bg-[#38BDF8] hover:bg-[#38BDF8]/90 text-[#101827] transition-all duration-300 transform hover:scale-105 hover:shadow-[0_0_15px_rgba(56,189,248,0.4)]">
<i class="fas fa-comment-alt mr-2 group-hover/chat:animate-bounce"></i>
Chat
</a>
<option value="{{$cfg.Name}}" class="bg-[#1E293B] text-[#E5E7EB]">{{$cfg.Name}}</option>
{{ end }}
{{ if eq . "FLAG_IMAGE" }}
<a href="text2image/{{$backendCfg.Name}}" class="flex-1 min-w-0 group/image inline-flex items-center justify-center rounded-xl px-4 py-3 text-sm font-semibold bg-green-600 hover:bg-green-700 text-white transition-all duration-300 transform hover:scale-105 hover:shadow-[0_0_15px_rgba(34,197,94,0.4)]">
<i class="fas fa-image mr-2 group-hover/image:animate-pulse"></i>
Image
</a>
{{ end }}
{{ if eq . "FLAG_TTS" }}
<a href="tts/{{$backendCfg.Name}}" class="flex-1 min-w-0 group/tts inline-flex items-center justify-center rounded-xl px-4 py-3 text-sm font-semibold bg-[#8B5CF6] hover:bg-[#8B5CF6]/90 text-white transition-all duration-300 transform hover:scale-105 hover:shadow-[0_0_15px_rgba(139,92,246,0.4)]">
<i class="fas fa-microphone mr-2 group-hover/tts:animate-pulse"></i>
TTS
</a>
{{ end }}
{{ end }}
</div>
<!-- Action Buttons -->
<div class="flex justify-between items-center pt-4 border-t border-[#101827]">
<div class="flex gap-2">
{{ if index $loadedModels .Name }}
<button class="group/stop 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"
data-twe-ripple-init=""
onclick="handleStopModel('{{.Name}}')">
<i class="fas fa-stop mr-2 group-hover/stop:animate-pulse"></i>Stop
</button>
{{ end }}
</div>
<div class="flex gap-2">
<a href="/models/edit/{{.Name}}"
class="group/edit inline-flex items-center text-sm font-semibold text-[#38BDF8] hover:text-[#8B5CF6] hover:bg-[#38BDF8]/10 rounded-lg px-3 py-2 transition-all duration-200">
<i class="fas fa-edit mr-2 group-hover/edit:animate-pulse"></i>Edit
</a>
<button
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"
data-twe-ripple-init=""
onclick="handleDeleteModel('{{.Name}}')">
<i class="fas fa-trash-alt mr-2 group-hover/delete:animate-bounce"></i>Delete
</button>
</div>
</div>
</div>
</div>
{{ end }}
<!-- Models without config -->
{{ range .Models }}
<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">
<i class="fas fa-brain text-2xl text-[#94A3B8]"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-bold text-xl text-[#E5E7EB] truncate mb-2">{{.}}</h3>
<div class="flex flex-wrap gap-2 mb-4">
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-yellow-500/10 text-yellow-300 border border-yellow-500/30">
<i class="fas fa-magic mr-1"></i>Auto Backend
</span>
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-orange-500/10 text-orange-300 border border-orange-500/30">
<i class="fas fa-exclamation-triangle mr-1"></i>No Config
</span>
</div>
<div class="flex justify-center pt-4">
<span class="inline-flex items-center text-sm font-medium text-[#94A3B8] px-4 py-2 bg-[#101827]/50 rounded-lg">
<i class="fas fa-info-circle mr-2"></i>
Configuration required for full functionality
</span>
</div>
</div>
</div>
</div>
</div>
{{end}}
</div>
{{ end }}
</div>
<!-- Backends Section -->
<div class="mt-12">
<div class="mb-8">
<h2 class="text-3xl md:text-4xl font-bold text-[#E5E7EB] mb-2 flex items-center">
<i class="fas fa-cogs mr-3 text-[#8B5CF6]"></i>
Installed Backends
</h2>
<p class="text-[#94A3B8]">
<span class="text-[#8B5CF6] font-semibold">{{len .InstalledBackends}}</span> backend{{if gt (len .InstalledBackends) 1}}s{{end}} ready to use
</p>
</div>
{{ 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>
<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>
{{ end }}
</select>
</div>
{{end}}
<!-- Input Bar -->
<form @submit.prevent="startChat($event)" class="relative w-full">
<div class="relative w-full bg-[#1E293B] border border-[#38BDF8]/20 rounded-xl">
<textarea
x-model="inputValue"
placeholder="Send a message..."
class="p-4 pr-32 w-full bg-[#1E293B] text-[#E5E7EB] placeholder-[#94A3B8] focus:outline-none resize-none border-0 rounded-xl transition-colors focus:ring-2 focus:ring-[#38BDF8]/50"
required
@keydown.shift="shiftPressed = true"
@keyup.shift="shiftPressed = false"
@keydown.enter.prevent="if (!shiftPressed && selectedModel && inputValue.trim()) { startChat($event); }"
rows="2"
></textarea>
<span x-show="fileName" x-text="fileName" class="absolute right-28 top-4 text-[#94A3B8] text-xs"></span>
<!-- Attachment Buttons -->
<button
type="button"
@click="document.getElementById('index_input_image').click()"
class="fa-solid fa-image text-[#94A3B8] absolute right-20 top-4 text-base p-1.5 hover:text-[#38BDF8] transition-colors"
title="Attach images"
></button>
<button
type="button"
@click="document.getElementById('index_input_audio').click()"
class="fa-solid fa-microphone text-[#94A3B8] absolute right-28 top-4 text-base p-1.5 hover:text-[#38BDF8] transition-colors"
title="Attach an audio file"
></button>
<button
type="button"
@click="document.getElementById('index_input_file').click()"
class="fa-solid fa-file text-[#94A3B8] absolute right-36 top-4 text-base p-1.5 hover:text-[#38BDF8] transition-colors"
title="Upload text, markdown or PDF file"
></button>
<!-- Send Button -->
<button
type="submit"
:disabled="!selectedModel || !inputValue.trim()"
:class="!selectedModel || !inputValue.trim() ? 'opacity-50 cursor-not-allowed' : ''"
class="text-lg p-2 text-[#94A3B8] hover:text-[#38BDF8] transition-colors absolute right-3 top-4"
title="Send message (Enter)"
>
<i class="fa-solid fa-paper-plane"></i>
</button>
</div>
</form>
<!-- Hidden File Inputs -->
<input
id="index_input_image"
type="file"
multiple
accept="image/*"
style="display: none;"
@change="imageFiles = Array.from($event.target.files); fileName = imageFiles.length > 0 ? imageFiles.length + ' image(s) selected' : ''"
/>
<input
id="index_input_audio"
type="file"
multiple
accept="audio/*"
style="display: none;"
@change="audioFiles = Array.from($event.target.files); fileName = audioFiles.length > 0 ? audioFiles.length + ' audio file(s) selected' : ''"
/>
<input
id="index_input_file"
type="file"
multiple
accept=".txt,.md,.pdf"
style="display: none;"
@change="textFiles = Array.from($event.target.files); fileName = textFiles.length > 0 ? textFiles.length + ' file(s) selected' : ''"
/>
</div>
<!-- Quick Links -->
<div class="flex flex-wrap justify-center gap-3 mb-8">
<a href="/manage"
class="inline-flex items-center text-sm text-[#94A3B8] hover:text-[#E5E7EB] px-4 py-2 rounded-lg hover:bg-[#1E293B] transition-colors">
<i class="fas fa-cog mr-2"></i>
Manage Models
</a>
<a href="/import-model" class="inline-flex items-center text-sm text-[#94A3B8] hover:text-[#E5E7EB] px-4 py-2 rounded-lg hover:bg-[#1E293B] transition-colors">
<i class="fas fa-upload mr-2"></i>
Import Model
</a>
<a href="/browse/"
class="inline-flex items-center text-sm text-[#94A3B8] hover:text-[#E5E7EB] px-4 py-2 rounded-lg hover:bg-[#1E293B] transition-colors">
<i class="fas fa-images mr-2"></i>
Browse Gallery
</a>
<a href="https://localai.io" target="_blank"
class="inline-flex items-center text-sm text-[#94A3B8] hover:text-[#E5E7EB] px-4 py-2 rounded-lg hover:bg-[#1E293B] transition-colors">
<i class="fas fa-book mr-2"></i>
Documentation
</a>
</div>
{{ end }}
</div>
@@ -444,166 +245,95 @@
</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;
// Handle form submission - redirect to chat with message
function startChat(event) {
if (event) {
event.preventDefault();
}
try {
const response = await fetch('/backend/shutdown', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ model: modelName })
});
if (response.ok) {
window.location.reload();
} else {
alert('Failed to stop model');
}
} catch (error) {
console.error('Error stopping model:', error);
alert('Failed to stop model');
}
}
async function handleDeleteModel(modelName) {
if (!confirm('Are you sure you wish to delete this model?')) {
// Get form data directly from form elements (Alpine x-model binds to value)
const form = event ? event.target.closest('form') : document.querySelector('form');
if (!form) return;
const select = form.closest('[x-data]').querySelector('select');
const textarea = form.querySelector('textarea');
const selectedModel = select ? select.value : '';
const message = textarea ? textarea.value : '';
if (!selectedModel || !message.trim()) {
return;
}
try {
const response = await fetch(`/api/models/delete/${encodeURIComponent(modelName)}`, {
method: 'POST'
});
if (response.ok) {
window.location.reload();
} else {
alert('Failed to delete model');
}
} catch (error) {
console.error('Error deleting model:', error);
alert('Failed to delete model');
}
}
// Handle reload models button
document.addEventListener('DOMContentLoaded', function() {
const reloadBtn = document.getElementById('reload-models-btn');
if (reloadBtn) {
reloadBtn.addEventListener('click', function() {
const button = this;
const originalText = button.querySelector('span').textContent;
const icon = button.querySelector('i');
// Store message and files in localStorage for chat page to pick up
const chatData = {
message: message,
imageFiles: [],
audioFiles: [],
textFiles: []
};
// Show loading state
button.disabled = true;
button.querySelector('span').textContent = 'Updating...';
icon.classList.add('fa-spin');
// Convert files to base64 for storage
const imageInput = document.getElementById('index_input_image');
const audioInput = document.getElementById('index_input_audio');
const fileInput = document.getElementById('index_input_file');
// Make the API call
fetch('/models/reload', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
const filePromises = [
...Array.from(imageInput.files || []).map(file =>
new Promise(resolve => {
const reader = new FileReader();
reader.onload = e => resolve({ name: file.name, data: e.target.result, type: file.type });
reader.readAsDataURL(file);
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Show success state briefly
button.querySelector('span').textContent = 'Updated!';
icon.classList.remove('fa-spin', 'fa-sync-alt');
icon.classList.add('fa-check');
),
...Array.from(audioInput.files || []).map(file =>
new Promise(resolve => {
const reader = new FileReader();
reader.onload = e => resolve({ name: file.name, data: e.target.result, type: file.type });
reader.readAsDataURL(file);
})
),
...Array.from(fileInput.files || []).map(file =>
new Promise(resolve => {
const reader = new FileReader();
reader.onload = e => resolve({ name: file.name, data: e.target.result, type: file.type });
reader.readAsText(file);
})
)
];
// Reload the page after a short delay
setTimeout(() => {
window.location.reload();
}, 1000);
if (filePromises.length > 0) {
Promise.all(filePromises).then(files => {
files.forEach(file => {
if (file.type.startsWith('image/')) {
chatData.imageFiles.push(file);
} else if (file.type.startsWith('audio/')) {
chatData.audioFiles.push(file);
} else {
// Show error state
button.querySelector('span').textContent = 'Error!';
icon.classList.remove('fa-spin');
console.error('Failed to reload models:', data.error);
// Reset button after delay
setTimeout(() => {
button.disabled = false;
button.querySelector('span').textContent = originalText;
icon.classList.remove('fa-check');
icon.classList.add('fa-sync-alt');
}, 3000);
chatData.textFiles.push(file);
}
})
.catch(error => {
// Show error state
button.querySelector('span').textContent = 'Error!';
icon.classList.remove('fa-spin');
console.error('Error reloading models:', error);
// Reset button after delay
setTimeout(() => {
button.disabled = false;
button.querySelector('span').textContent = originalText;
icon.classList.remove('fa-check');
icon.classList.add('fa-sync-alt');
}, 3000);
});
// Store in localStorage
localStorage.setItem('localai_index_chat_data', JSON.stringify(chatData));
// Redirect to chat page
window.location.href = `/chat/${selectedModel}`;
}).catch(err => {
console.error('Error processing files:', err);
// Still redirect even if file processing fails
localStorage.setItem('localai_index_chat_data', JSON.stringify({ message: message, imageFiles: [], audioFiles: [], textFiles: [] }));
window.location.href = `/chat/${selectedModel}`;
});
} else {
// No files, just store message and redirect
localStorage.setItem('localai_index_chat_data', JSON.stringify(chatData));
window.location.href = `/chat/${selectedModel}`;
}
});
}
// Make startChat available globally
window.startChat = startChat;
</script>
</body>

View File

@@ -9,7 +9,7 @@
<div class="container mx-auto px-4 py-8 flex-grow flex items-center justify-center">
<!-- Auth Card -->
<div class="max-w-md w-full bg-[#1E293B] border border-[#38BDF8]/20 rounded-xl overflow-hidden shadow-2xl shadow-[#38BDF8]/10">
<div class="max-w-md w-full bg-[#1E293B] border border-[#38BDF8]/20 rounded-xl overflow-hidden">
<div class="animation-container">
<div class="text-overlay">
<img src="static/logo.png" alt="LocalAI Logo" class="h-32 drop-shadow-[0_0_15px_rgba(56,189,248,0.3)]">
@@ -47,11 +47,10 @@
<div>
<button
type="submit"
class="group w-full flex items-center justify-center bg-[#38BDF8] hover:bg-[#38BDF8]/90 text-[#101827] font-semibold py-3 px-6 rounded-lg transition duration-300 ease-in-out transform hover:scale-[1.02] hover:shadow-[0_0_20px_rgba(56,189,248,0.4)]"
class="w-full flex items-center justify-center bg-[#38BDF8] hover:bg-[#38BDF8]/90 text-[#101827] font-semibold py-3 px-6 rounded-lg transition-colors"
>
<i class="fas fa-sign-in-alt mr-2"></i>
<span>Login</span>
<i class="fas fa-arrow-right opacity-0 group-hover:opacity-100 group-hover:translate-x-2 ml-2 transition-all duration-300"></i>
</button>
</div>
</form>

588
core/http/views/manage.html Normal file
View File

@@ -0,0 +1,588 @@
<!DOCTYPE html>
<html lang="en">
{{template "views/partials/head" .}}
<body class="bg-[#101827] text-[#E5E7EB]">
<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="transition ease-out duration-200"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
:class="notification.type === 'error' ? 'bg-red-500' : 'bg-green-500'"
class="rounded-lg 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:opacity-80 transition-opacity">
<i class="fas fa-times"></i>
</button>
</div>
</template>
</div>
<div class="container mx-auto px-4 py-8 flex-grow">
<!-- Header -->
<div class="mb-8">
<h1 class="text-4xl md:text-5xl font-bold text-[#E5E7EB] mb-2">
<span class="bg-clip-text text-transparent bg-gradient-to-r from-[#38BDF8] via-[#8B5CF6] to-[#38BDF8]">
Model & Backend Management
</span>
</h1>
<p class="text-lg text-[#94A3B8]">Manage your installed models and backends</p>
</div>
<!-- Quick Actions -->
<div class="flex flex-wrap gap-4 mb-8">
<a href="browse"
class="inline-flex items-center bg-[#8B5CF6] hover:bg-[#8B5CF6]/90 text-white py-2 px-6 rounded-lg font-semibold transition-colors">
<i class="fas fa-images mr-2"></i>
<span>Model Gallery</span>
</a>
<a href="/import-model"
class="inline-flex items-center bg-green-600 hover:bg-green-700 text-white py-2 px-6 rounded-lg font-semibold transition-colors">
<i class="fas fa-plus mr-2"></i>
<span>Import Model</span>
</a>
<button id="reload-models-btn"
class="inline-flex items-center bg-orange-600 hover:bg-orange-700 text-white py-2 px-6 rounded-lg font-semibold transition-colors">
<i class="fas fa-sync-alt mr-2"></i>
<span>Update Models</span>
</button>
<a href="/browse/backends"
class="inline-flex items-center bg-[#1E293B] hover:bg-[#1E293B]/80 border border-[#8B5CF6]/20 text-[#E5E7EB] py-2 px-6 rounded-lg font-semibold transition-colors">
<i class="fas fa-cogs mr-2"></i>
<span>Backend Gallery</span>
</a>
</div>
<!-- Models Section -->
<div class="models mt-8">
{{template "views/partials/inprogress" .}}
{{ if eq (len .ModelsConfig) 0 }}
<!-- No Models State -->
<div class="bg-[#1E293B] border border-[#38BDF8]/20 rounded-xl p-12">
<div class="text-center max-w-4xl mx-auto">
<div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-yellow-500/10 border border-yellow-500/20 mb-6">
<i class="text-yellow-400 text-2xl fas fa-robot"></i>
</div>
<h2 class="text-3xl md:text-4xl font-bold text-[#E5E7EB] mb-4">No models installed yet</h2>
<p class="text-xl text-[#94A3B8] mb-8">Get started by installing a model from the gallery or importing it</p>
<div class="flex flex-wrap justify-center gap-4 mb-8">
<a href="browse" class="inline-flex items-center bg-[#38BDF8] hover:bg-[#38BDF8]/90 text-[#101827] py-3 px-6 rounded-lg font-semibold transition-colors">
<i class="fas fa-images mr-2"></i>
Browse Model Gallery
</a>
<a href="/import-model" class="inline-flex items-center bg-green-600 hover:bg-green-700 text-white py-3 px-6 rounded-lg font-semibold transition-colors">
<i class="fas fa-upload mr-2"></i>
Import Model
</a>
<a href="https://localai.io/basics/getting_started/" target="_blank" class="inline-flex items-center bg-[#1E293B] hover:bg-[#1E293B]/80 border border-[#38BDF8]/20 text-[#E5E7EB] py-3 px-6 rounded-lg font-semibold transition-colors">
<i class="fas fa-book mr-2"></i>
Documentation
</a>
</div>
{{ if ne (len .Models) 0 }}
<div class="mt-12 pt-8 border-t border-[#38BDF8]/20">
<h3 class="text-2xl font-bold text-[#E5E7EB] mb-4 flex items-center">
<i class="fas fa-file-alt mr-2 text-[#38BDF8]"></i>
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-[#38BDF8]/20 rounded-lg p-4 flex items-center">
<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>
<div class="flex-1">
<p class="font-semibold text-[#E5E7EB] truncate">{{.}}</p>
<p class="text-xs text-[#94A3B8]">No configuration</p>
</div>
</div>
{{end}}
</div>
</div>
{{end}}
</div>
</div>
{{ else }}
<!-- Models Grid -->
{{ $modelsN := len .ModelsConfig}}
{{ $modelsN = add $modelsN (len .Models)}}
<div class="mb-8 flex flex-col md:flex-row md:items-center md:justify-between">
<div class="mb-4 md:mb-0">
<h2 class="text-3xl md:text-4xl font-bold text-[#E5E7EB] mb-2 flex items-center">
<i class="fas fa-brain mr-3 text-[#38BDF8]"></i>
Installed Models
</h2>
<p class="text-[#94A3B8]">
<span class="text-[#38BDF8] font-semibold">{{$modelsN}}</span> model{{if gt $modelsN 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">
{{$galleryConfig:=.GalleryConfig}}
{{ $loadedModels := .LoadedModels }}
{{ range .ModelsConfig }}
{{ $backendCfg := . }}
{{ $cfg:= index $galleryConfig .Name}}
<div class="bg-[#1E293B] border border-[#38BDF8]/20 rounded-xl p-6">
<!-- Card Header -->
<div class="flex items-start space-x-4 mb-4">
<div class="relative w-12 h-12 rounded-lg flex-shrink-0 bg-[#101827] flex items-center justify-center">
{{ if and $cfg $cfg.Icon }}
<img src="{{$cfg.Icon}}"
class="w-full h-full object-contain"
alt="{{.Name}} icon"
>
{{ else }}
<i class="fas fa-brain text-xl text-[#38BDF8]"></i>
{{ end }}
{{ if index $loadedModels .Name }}
<div class="absolute -top-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-[#1E293B]"></div>
{{ end }}
</div>
<div class="flex-1 min-w-0">
<h3 class="font-bold text-lg text-[#E5E7EB] truncate mb-2">{{.Name}}</h3>
<div class="flex flex-wrap gap-2">
{{ if .Backend }}
<span class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-[#38BDF8]/10 text-[#38BDF8]">
<i class="fas fa-cog mr-1 text-xs"></i>{{.Backend}}
</span>
{{ else }}
<span class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-yellow-500/10 text-yellow-300">
<i class="fas fa-magic mr-1 text-xs"></i>Auto
</span>
{{ end }}
{{ if and $backendCfg (or (ne $backendCfg.MCP.Servers "") (ne $backendCfg.MCP.Stdio "")) }}
<span class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-[#8B5CF6]/10 text-[#8B5CF6]">
<i class="fas fa-plug mr-1 text-xs"></i>MCP
</span>
{{ end }}
{{ if index $loadedModels .Name }}
<span class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-green-500/10 text-green-300">
<i class="fas fa-play mr-1 text-xs"></i>Running
</span>
{{ end }}
</div>
</div>
</div>
<!-- Usage Buttons -->
<div class="flex flex-wrap gap-2 mb-4">
{{ range .KnownUsecaseStrings }}
{{ if eq . "FLAG_CHAT" }}
<a href="chat/{{$backendCfg.Name}}" class="flex-1 min-w-0 inline-flex items-center justify-center rounded-lg px-4 py-2 text-sm font-semibold bg-[#38BDF8] hover:bg-[#38BDF8]/90 text-[#101827] transition-colors">
<i class="fas fa-comment-alt mr-2"></i>
Chat
</a>
{{ end }}
{{ if eq . "FLAG_IMAGE" }}
<a href="text2image/{{$backendCfg.Name}}" class="flex-1 min-w-0 inline-flex items-center justify-center rounded-lg px-4 py-2 text-sm font-semibold bg-green-600 hover:bg-green-700 text-white transition-colors">
<i class="fas fa-image mr-2"></i>
Image
</a>
{{ end }}
{{ if eq . "FLAG_TTS" }}
<a href="tts/{{$backendCfg.Name}}" class="flex-1 min-w-0 inline-flex items-center justify-center rounded-lg px-4 py-2 text-sm font-semibold bg-[#8B5CF6] hover:bg-[#8B5CF6]/90 text-white transition-colors">
<i class="fas fa-microphone mr-2"></i>
TTS
</a>
{{ end }}
{{ end }}
</div>
<!-- Action Buttons -->
<div class="flex justify-between items-center pt-4 border-t border-[#101827]">
<div class="flex gap-2">
{{ if index $loadedModels .Name }}
<button class="inline-flex items-center text-sm font-medium text-red-400 hover:text-red-300 hover:bg-red-500/10 rounded-lg px-3 py-2 transition-colors"
data-twe-ripple-init=""
onclick="handleStopModel('{{.Name}}')">
<i class="fas fa-stop mr-2"></i>Stop
</button>
{{ end }}
</div>
<div class="flex gap-2">
<a href="/models/edit/{{.Name}}"
class="inline-flex items-center text-sm font-medium text-[#38BDF8] hover:text-[#8B5CF6] hover:bg-[#38BDF8]/10 rounded-lg px-3 py-2 transition-colors">
<i class="fas fa-edit mr-2"></i>Edit
</a>
<button
class="inline-flex items-center text-sm font-medium text-red-400 hover:text-red-300 hover:bg-red-500/10 rounded-lg px-3 py-2 transition-colors"
data-twe-ripple-init=""
onclick="handleDeleteModel('{{.Name}}')">
<i class="fas fa-trash-alt mr-2"></i>Delete
</button>
</div>
</div>
</div>
{{ end }}
<!-- Models without config -->
{{ range .Models }}
<div class="bg-[#1E293B] border border-[#38BDF8]/20 rounded-xl p-6">
<div class="flex items-start space-x-4">
<div class="w-12 h-12 rounded-lg flex-shrink-0 bg-[#101827] flex items-center justify-center">
<i class="fas fa-brain text-xl text-[#94A3B8]"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-bold text-lg text-[#E5E7EB] truncate mb-2">{{.}}</h3>
<div class="flex flex-wrap gap-2 mb-4">
<span class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-yellow-500/10 text-yellow-300">
<i class="fas fa-magic mr-1 text-xs"></i>Auto Backend
</span>
<span class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-orange-500/10 text-orange-300">
<i class="fas fa-exclamation-triangle mr-1 text-xs"></i>No Config
</span>
</div>
<div class="flex justify-center pt-2">
<span class="inline-flex items-center text-sm text-[#94A3B8] px-3 py-2 bg-[#101827]/50 rounded-lg">
<i class="fas fa-info-circle mr-2"></i>
Configuration required for full functionality
</span>
</div>
</div>
</div>
</div>
{{end}}
</div>
{{ end }}
</div>
<!-- Backends Section -->
<div class="mt-12">
<div class="mb-8">
<h2 class="text-3xl md:text-4xl font-bold text-[#E5E7EB] mb-2 flex items-center">
<i class="fas fa-cogs mr-3 text-[#8B5CF6]"></i>
Installed Backends
</h2>
<p class="text-[#94A3B8]">
<span class="text-[#8B5CF6] font-semibold">{{len .InstalledBackends}}</span> backend{{if gt (len .InstalledBackends) 1}}s{{end}} ready to use
</p>
</div>
{{ if eq (len .InstalledBackends) 0 }}
<!-- No backends state -->
<div class="bg-[#1E293B] border border-[#8B5CF6]/20 rounded-xl p-12">
<div class="text-center max-w-4xl mx-auto">
<div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-[#8B5CF6]/10 border border-[#8B5CF6]/20 mb-6">
<i class="text-[#8B5CF6] text-2xl fas fa-cogs"></i>
</div>
<h2 class="text-3xl md:text-4xl font-bold text-[#E5E7EB] mb-4">No backends installed yet</h2>
<p class="text-xl text-[#94A3B8] mb-8">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-lg font-semibold transition-colors">
<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-lg font-semibold transition-colors">
<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="bg-[#1E293B] border border-[#8B5CF6]/20 rounded-xl p-6">
<!-- Card Header -->
<div class="flex items-start space-x-4 mb-4">
<div class="w-12 h-12 rounded-lg flex-shrink-0 bg-[#101827] flex items-center justify-center">
<i class="fas fa-cog text-xl text-[#8B5CF6]"></i>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-bold text-lg text-[#E5E7EB] truncate mb-2">{{.Name}}</h3>
<div class="flex flex-wrap gap-2">
{{ if .IsSystem }}
<span class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-blue-500/10 text-blue-300">
<i class="fas fa-shield-alt mr-1 text-xs"></i>System
</span>
{{ else }}
<span class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-green-500/10 text-green-300">
<i class="fas fa-download mr-1 text-xs"></i>User Installed
</span>
{{ end }}
{{ if .IsMeta }}
<span class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-[#8B5CF6]/10 text-[#8B5CF6]">
<i class="fas fa-layer-group mr-1 text-xs"></i>Meta
</span>
{{ end }}
</div>
</div>
</div>
<!-- Backend Details -->
<div class="space-y-3 text-sm mb-4">
{{ 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 false }}
{{ 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 transition-colors">
{{.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 flex-shrink-0"></i>
<div class="flex-1 min-w-0">
<span class="text-[#94A3B8]">Path:</span>
<span class="text-[#E5E7EB] ml-1 text-xs font-mono truncate inline-block max-w-full" title="{{.RunFile}}">{{.RunFile}}</span>
</div>
</div>
{{ end }}
</div>
<!-- Action Buttons -->
{{ if not .IsSystem }}
<div class="flex justify-end items-center pt-4 border-t border-[#101827]">
<button
@click="deleteBackend('{{.Name}}')"
class="inline-flex items-center text-sm font-medium text-red-400 hover:text-red-300 hover:bg-red-500/10 rounded-lg px-3 py-2 transition-colors">
<i class="fas fa-trash-alt mr-2"></i>Delete
</button>
</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;
}
try {
const response = await fetch('/backend/shutdown', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ model: modelName })
});
if (response.ok) {
window.location.reload();
} else {
alert('Failed to stop model');
}
} catch (error) {
console.error('Error stopping model:', error);
alert('Failed to stop model');
}
}
async function handleDeleteModel(modelName) {
if (!confirm('Are you sure you wish to delete this model?')) {
return;
}
try {
const response = await fetch(`/api/models/delete/${encodeURIComponent(modelName)}`, {
method: 'POST'
});
if (response.ok) {
window.location.reload();
} else {
alert('Failed to delete model');
}
} catch (error) {
console.error('Error deleting model:', error);
alert('Failed to delete model');
}
}
// Handle reload models button
document.addEventListener('DOMContentLoaded', function() {
const reloadBtn = document.getElementById('reload-models-btn');
if (reloadBtn) {
reloadBtn.addEventListener('click', function() {
const button = this;
const originalText = button.querySelector('span').textContent;
const icon = button.querySelector('i');
// Show loading state
button.disabled = true;
button.querySelector('span').textContent = 'Updating...';
icon.classList.add('fa-spin');
// Make the API call
fetch('/models/reload', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Show success state briefly
button.querySelector('span').textContent = 'Updated!';
icon.classList.remove('fa-spin', 'fa-sync-alt');
icon.classList.add('fa-check');
// Reload the page after a short delay
setTimeout(() => {
window.location.reload();
}, 1000);
} else {
// Show error state
button.querySelector('span').textContent = 'Error!';
icon.classList.remove('fa-spin');
console.error('Failed to reload models:', data.error);
// Reset button after delay
setTimeout(() => {
button.disabled = false;
button.querySelector('span').textContent = originalText;
icon.classList.remove('fa-check');
icon.classList.add('fa-sync-alt');
}, 3000);
}
})
.catch(error => {
// Show error state
button.querySelector('span').textContent = 'Error!';
icon.classList.remove('fa-spin');
console.error('Error reloading models:', error);
// Reset button after delay
setTimeout(() => {
button.disabled = false;
button.querySelector('span').textContent = originalText;
icon.classList.remove('fa-check');
icon.classList.add('fa-sync-alt');
}, 3000);
});
});
}
});
</script>
</body>
</html>

View File

@@ -10,45 +10,36 @@
<div class="container mx-auto px-4 py-8 flex-grow">
<!-- Hero Header -->
<div class="relative bg-[#1E293B] border border-[#8B5CF6]/20 rounded-3xl shadow-2xl shadow-[#8B5CF6]/10 p-8 mb-8 overflow-hidden">
<!-- Background Pattern -->
<div class="absolute inset-0 opacity-10">
<div class="absolute inset-0 bg-gradient-to-r from-[#8B5CF6]/20 to-[#38BDF8]/20"></div>
<div class="absolute top-0 left-0 w-full h-full" style="background-image: radial-gradient(circle at 1px 1px, rgba(139,92,246,0.15) 1px, transparent 0); background-size: 20px 20px;"></div>
</div>
<div class="relative max-w-5xl mx-auto">
<div class="bg-[#1E293B] border border-[#8B5CF6]/20 rounded-xl p-8 mb-8">
<div class="max-w-5xl mx-auto">
<div class="flex flex-col md:flex-row md:items-center md:justify-between">
<div class="mb-4 md:mb-0">
<h1 class="text-3xl md:text-4xl font-bold text-white mb-2">
<h1 class="text-3xl md:text-4xl font-bold text-[#E5E7EB] mb-2">
<span class="bg-clip-text text-transparent bg-gradient-to-r from-violet-400 via-purple-400 to-fuchsia-400">
{{if .ModelName}}Edit Model: {{.ModelName}}{{else}}Import New Model{{end}}
</span>
</h1>
<p class="text-lg text-gray-300 font-light" x-text="isAdvancedMode ? 'Configure your model settings using YAML' : 'Import a model from URI with preferences'"></p>
<p class="text-lg text-[#94A3B8] font-light" x-text="isAdvancedMode ? 'Configure your model settings using YAML' : 'Import a model from URI with preferences'"></p>
</div>
<div class="flex gap-3">
<!-- Mode Toggle (only show when not in edit mode) -->
<template x-if="!isEditMode">
<button @click="toggleMode()"
class="group relative inline-flex items-center bg-gradient-to-r from-gray-600 to-gray-700 hover:from-gray-700 hover:to-gray-800 text-white py-3 px-6 rounded-xl font-semibold transition-all duration-300 ease-in-out transform hover:scale-105 hover:shadow-xl">
<i class="fas group-hover:animate-pulse" :class="isAdvancedMode ? 'fa-magic mr-2' : 'fa-code mr-2'"></i>
class="inline-flex items-center bg-[#1E293B] hover:bg-[#1E293B]/80 border border-[#8B5CF6]/20 text-[#E5E7EB] py-3 px-6 rounded-lg font-semibold transition-colors">
<i class="fas" :class="isAdvancedMode ? 'fa-magic mr-2' : 'fa-code mr-2'"></i>
<span x-text="isAdvancedMode ? 'Simple Mode' : 'Advanced Mode'"></span>
<div class="absolute inset-0 rounded-xl bg-white/10 opacity-0 group-hover:opacity-100 transition-opacity"></div>
</button>
</template>
<!-- Advanced Mode Buttons -->
<template x-if="isAdvancedMode">
<div class="flex gap-3">
<button id="validateBtn" class="group relative inline-flex items-center bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 text-white py-3 px-6 rounded-xl font-semibold transition-all duration-300 ease-in-out transform hover:scale-105 hover:shadow-xl hover:shadow-blue-500/25">
<i class="fas fa-check mr-2 group-hover:animate-pulse"></i>
<button id="validateBtn" class="inline-flex items-center bg-blue-600 hover:bg-blue-700 text-white py-3 px-6 rounded-lg font-semibold transition-colors">
<i class="fas fa-check mr-2"></i>
<span>Validate</span>
<div class="absolute inset-0 rounded-xl bg-white/10 opacity-0 group-hover:opacity-100 transition-opacity"></div>
</button>
<button id="saveBtn" class="group relative inline-flex items-center bg-gradient-to-r from-green-600 to-emerald-600 hover:from-green-700 hover:to-emerald-700 text-white py-3 px-6 rounded-xl font-semibold transition-all duration-300 ease-in-out transform hover:scale-105 hover:shadow-xl hover:shadow-green-500/25">
<i class="fas fa-save mr-2 group-hover:animate-pulse"></i>
<button id="saveBtn" class="inline-flex items-center bg-green-600 hover:bg-green-700 text-white py-3 px-6 rounded-lg font-semibold transition-colors">
<i class="fas fa-save mr-2"></i>
<span>{{if .ModelName}}Update{{else}}Create{{end}}</span>
<div class="absolute inset-0 rounded-xl bg-white/10 opacity-0 group-hover:opacity-100 transition-opacity"></div>
</button>
</div>
</template>
@@ -57,10 +48,9 @@
<button @click="submitImport()"
:disabled="isSubmitting || !importUri.trim()"
:class="(isSubmitting || !importUri.trim()) ? 'opacity-50 cursor-not-allowed' : ''"
class="group relative inline-flex items-center bg-gradient-to-r from-green-600 to-emerald-600 hover:from-green-700 hover:to-emerald-700 text-white py-3 px-6 rounded-xl font-semibold transition-all duration-300 ease-in-out transform hover:scale-105 hover:shadow-xl hover:shadow-green-500/25">
<i class="fas group-hover:animate-pulse" :class="isSubmitting ? 'fa-spinner fa-spin mr-2' : 'fa-upload mr-2'"></i>
class="inline-flex items-center bg-green-600 hover:bg-green-700 text-white py-3 px-6 rounded-lg font-semibold transition-colors">
<i class="fas" :class="isSubmitting ? 'fa-spinner fa-spin mr-2' : 'fa-upload mr-2'"></i>
<span x-text="isSubmitting ? 'Importing...' : 'Import Model'"></span>
<div class="absolute inset-0 rounded-xl bg-white/10 opacity-0 group-hover:opacity-100 transition-opacity"></div>
</button>
</template>
</div>
@@ -73,15 +63,13 @@
<!-- Simple Import Mode -->
<div x-show="!isAdvancedMode && !isEditMode"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 transform translate-y-4"
x-transition:enter-end="opacity-100 transform translate-y-0"
class="relative bg-gradient-to-br from-gray-800/90 to-gray-900/90 border border-gray-700/50 rounded-2xl overflow-hidden shadow-xl backdrop-blur-sm p-8">
<div class="absolute inset-0 rounded-2xl bg-gradient-to-br from-green-500/5 to-emerald-500/5"></div>
<div class="relative space-y-6">
<h2 class="text-2xl font-semibold text-white flex items-center gap-3 mb-6">
<div class="w-10 h-10 rounded-lg bg-green-500/20 flex items-center justify-center">
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
class="bg-[#1E293B] border border-[#8B5CF6]/20 rounded-xl p-8">
<div class="space-y-6">
<h2 class="text-2xl font-semibold text-[#E5E7EB] flex items-center gap-3 mb-6">
<div class="w-10 h-10 rounded-lg bg-green-500/10 flex items-center justify-center">
<i class="fas fa-link text-green-400"></i>
</div>
Import from URI
@@ -89,16 +77,16 @@
<!-- URI Input -->
<div>
<label class="block text-sm font-medium text-gray-300 mb-2">
<label class="block text-sm font-medium text-[#94A3B8] mb-2">
<i class="fas fa-link mr-2"></i>Model URI
</label>
<input
x-model="importUri"
type="text"
placeholder="https://example.com/model.gguf or file:///path/to/model.gguf"
class="w-full px-4 py-3 bg-gray-900/90 border border-gray-700/70 rounded-xl text-gray-200 focus:border-green-500 focus:ring-2 focus:ring-green-500/50 focus:outline-none transition-all"
class="w-full px-4 py-3 bg-[#101827] border border-[#1E293B] rounded-lg text-[#E5E7EB] focus:border-green-500 focus:ring-2 focus:ring-green-500/50 focus:outline-none transition-colors"
:disabled="isSubmitting">
<p class="mt-2 text-xs text-gray-400">
<p class="mt-2 text-xs text-[#94A3B8]">
Enter the URI or path to the model file you want to import
</p>
</div>
@@ -283,25 +271,23 @@
<!-- Advanced YAML Editor Panel -->
<div x-show="isAdvancedMode || isEditMode"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 transform translate-y-4"
x-transition:enter-end="opacity-100 transform translate-y-0"
class="relative bg-gradient-to-br from-gray-800/90 to-gray-900/90 border border-gray-700/50 rounded-2xl overflow-hidden shadow-xl backdrop-blur-sm h-[calc(100vh-250px)]">
<div class="absolute inset-0 rounded-2xl bg-gradient-to-br from-fuchsia-500/5 to-purple-500/5"></div>
<div class="relative sticky top-0 bg-gray-800/95 border-b border-gray-700/50 p-6 flex items-center justify-between z-10 backdrop-blur-sm">
<h2 class="text-xl font-semibold text-white flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-fuchsia-500/20 flex items-center justify-center">
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
class="bg-[#1E293B] border border-[#8B5CF6]/20 rounded-xl overflow-hidden h-[calc(100vh-250px)]">
<div class="sticky top-0 bg-[#1E293B] border-b border-[#101827] p-6 flex items-center justify-between z-10">
<h2 class="text-xl font-semibold text-[#E5E7EB] flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-fuchsia-500/10 flex items-center justify-center">
<i class="fas fa-code text-fuchsia-400"></i>
</div>
YAML Configuration Editor
</h2>
<div class="flex items-center gap-3">
<button id="formatYamlBtn" class="group text-gray-400 hover:text-gray-200 text-sm px-3 py-1.5 rounded-lg hover:bg-gray-700/50 transition-all duration-200">
<i class="fas fa-indent mr-1.5 group-hover:animate-pulse"></i> Format
<button id="formatYamlBtn" class="text-[#94A3B8] hover:text-[#E5E7EB] text-sm px-3 py-1.5 rounded-lg hover:bg-[#101827] transition-colors">
<i class="fas fa-indent mr-1.5"></i> Format
</button>
<button id="copyYamlBtn" class="group text-gray-400 hover:text-gray-200 text-sm px-3 py-1.5 rounded-lg hover:bg-gray-700/50 transition-all duration-200">
<i class="fas fa-copy mr-1.5 group-hover:animate-bounce"></i> Copy
<button id="copyYamlBtn" class="text-[#94A3B8] hover:text-[#E5E7EB] text-sm px-3 py-1.5 rounded-lg hover:bg-[#101827] transition-colors">
<i class="fas fa-copy mr-1.5"></i> Copy
</button>
</div>
</div>
@@ -447,11 +433,9 @@
@keyframes slideInFromTop {
from {
transform: translateY(-20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}

View File

@@ -10,22 +10,22 @@
<!-- 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"
<div x-show="true"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100"
x-transition:leave-end="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">
class="rounded-lg 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">
<button @click="dismissNotification(notification.id)" class="flex-shrink-0 text-white hover:opacity-80 transition-opacity">
<i class="fas fa-times"></i>
</button>
</div>
@@ -35,35 +35,29 @@
<div class="container mx-auto px-4 py-8 flex-grow">
<!-- Hero Header -->
<div class="relative bg-[#1E293B] border border-[#38BDF8]/20 rounded-3xl shadow-2xl shadow-[#38BDF8]/10 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-[#38BDF8]/20 to-[#8B5CF6]/20"></div>
<div class="absolute top-0 left-0 w-full h-full" style="background-image: radial-gradient(circle at 1px 1px, rgba(56,189,248,0.15) 1px, transparent 0); background-size: 20px 20px;"></div>
</div>
<div class="relative max-w-5xl mx-auto text-center">
<div class="bg-[#1E293B] border border-[#38BDF8]/20 rounded-xl p-8 mb-12">
<div class="max-w-5xl mx-auto text-center">
<h1 class="text-4xl md:text-5xl font-bold text-[#E5E7EB] mb-4">
<span class="bg-clip-text text-transparent bg-gradient-to-r from-[#38BDF8] via-[#8B5CF6] to-[#38BDF8]">
Model Gallery
</span>
</h1>
<p class="text-lg md:text-xl text-gray-300 mb-6 font-light">
<p class="text-lg md:text-xl text-[#94A3B8] mb-6 font-light">
Discover and install AI models from our curated collection
</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-indigo-400 rounded-full mr-2 animate-pulse"></div>
<div class="flex items-center bg-[#101827] rounded-lg px-4 py-2">
<div class="w-2 h-2 bg-indigo-400 rounded-full mr-2"></div>
<span class="font-semibold text-indigo-300" x-text="availableModels"></span>
<span class="text-gray-300 ml-1">models available</span>
<span class="text-[#94A3B8] ml-1">models available</span>
</div>
<div class="flex items-center bg-white/10 rounded-full px-4 py-2">
<div class="w-2 h-2 bg-purple-400 rounded-full mr-2 animate-pulse"></div>
<div class="flex items-center bg-[#101827] rounded-lg px-4 py-2">
<div class="w-2 h-2 bg-purple-400 rounded-full mr-2"></div>
<span class="font-semibold text-purple-300" x-text="repositories.length"></span>
<span class="text-gray-300 ml-1">repositories</span>
<span class="text-[#94A3B8] ml-1">repositories</span>
</div>
<a href="https://localai.io/models/" target="_blank"
class="flex items-center bg-blue-600/80 hover:bg-blue-600 text-white px-4 py-2 rounded-full transition-all duration-300 hover:scale-105">
class="inline-flex items-center bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition-colors">
<i class="fas fa-info-circle mr-2"></i>
<span>Documentation</span>
<i class="fas fa-external-link-alt ml-2 text-xs"></i>
@@ -75,28 +69,26 @@
{{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-blue-500/5 to-purple-500/5"></div>
<div class="relative">
<div class="bg-[#1E293B] border border-[#38BDF8]/20 rounded-xl p-8 mb-8">
<div>
<!-- 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-blue-400"></i>
<h3 class="text-xl font-semibold text-[#E5E7EB] mb-4 flex items-center">
<i class="fas fa-search mr-3 text-[#38BDF8]"></i>
Find Your Perfect Model
</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>
<i class="fas fa-search text-[#94A3B8]"></i>
</div>
<input
x-model="searchTerm"
@input.debounce.500ms="fetchModels()"
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-blue-500 focus:ring-2 focus:ring-blue-500/50 focus:outline-none"
class="w-full pl-12 pr-16 py-4 text-base font-normal text-[#E5E7EB] bg-[#101827] border border-[#1E293B] rounded-lg transition-colors focus:text-[#E5E7EB] focus:bg-[#101827] focus:border-[#38BDF8] focus:ring-2 focus:ring-[#38BDF8]/50 focus:outline-none"
type="search"
placeholder="Search models by name, tag, or description...">
<span class="absolute right-4 top-4" x-show="loading">
<svg class="animate-spin h-6 w-6 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<svg class="animate-spin h-6 w-6 text-[#38BDF8]" 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>
@@ -106,49 +98,49 @@
<!-- Filter by Type -->
<div class="mb-8">
<h3 class="text-lg font-semibold text-white mb-4 flex items-center">
<i class="fas fa-filter mr-3 text-purple-400"></i>
<h3 class="text-lg font-semibold text-[#E5E7EB] mb-4 flex items-center">
<i class="fas fa-filter mr-3 text-[#8B5CF6]"></i>
Filter by Model Type
</h3>
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-8 gap-3">
<button @click="filterByTerm('tts')"
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">
<i class="fas fa-microphone mr-2 group-hover:animate-pulse"></i>
class="flex items-center justify-center rounded-lg px-4 py-3 text-sm font-semibold bg-indigo-600/20 hover:bg-indigo-600/30 text-indigo-300 border border-indigo-500/30 transition-colors">
<i class="fas fa-microphone mr-2"></i>
<span>TTS</span>
</button>
<button @click="filterByTerm('stablediffusion')"
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">
<i class="fas fa-image mr-2 group-hover:animate-pulse"></i>
class="flex items-center justify-center rounded-lg px-4 py-3 text-sm font-semibold bg-purple-600/20 hover:bg-purple-600/30 text-purple-300 border border-purple-500/30 transition-colors">
<i class="fas fa-image mr-2"></i>
<span>Image</span>
</button>
<button @click="filterByTerm('llm')"
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">
<i class="fas fa-comment-alt mr-2 group-hover:animate-bounce"></i>
class="flex items-center justify-center rounded-lg px-4 py-3 text-sm font-semibold bg-blue-600/20 hover:bg-blue-600/30 text-blue-300 border border-blue-500/30 transition-colors">
<i class="fas fa-comment-alt mr-2"></i>
<span>LLM</span>
</button>
<button @click="filterByTerm('multimodal')"
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">
<i class="fas fa-object-group mr-2 group-hover:animate-pulse"></i>
class="flex items-center justify-center rounded-lg px-4 py-3 text-sm font-semibold bg-green-600/20 hover:bg-green-600/30 text-green-300 border border-green-500/30 transition-colors">
<i class="fas fa-object-group mr-2"></i>
<span>Multimodal</span>
</button>
<button @click="filterByTerm('embedding')"
class="group flex items-center justify-center rounded-xl px-4 py-3 text-sm font-semibold bg-gradient-to-r from-cyan-600/80 to-cyan-700/80 hover:from-cyan-600 hover:to-cyan-700 text-cyan-100 border border-cyan-500/30 hover:border-cyan-400/50 transition-all duration-300 transform hover:scale-105 hover:shadow-lg hover:shadow-cyan-500/25">
<i class="fas fa-vector-square mr-2 group-hover:animate-pulse"></i>
class="flex items-center justify-center rounded-lg px-4 py-3 text-sm font-semibold bg-cyan-600/20 hover:bg-cyan-600/30 text-cyan-300 border border-cyan-500/30 transition-colors">
<i class="fas fa-vector-square mr-2"></i>
<span>Embedding</span>
</button>
<button @click="filterByTerm('rerank')"
class="group flex items-center justify-center rounded-xl px-4 py-3 text-sm font-semibold bg-gradient-to-r from-amber-600/80 to-amber-700/80 hover:from-amber-600 hover:to-amber-700 text-amber-100 border border-amber-500/30 hover:border-amber-400/50 transition-all duration-300 transform hover:scale-105 hover:shadow-lg hover:shadow-amber-500/25">
<i class="fas fa-sort-amount-up mr-2 group-hover:animate-pulse"></i>
class="flex items-center justify-center rounded-lg px-4 py-3 text-sm font-semibold bg-amber-600/20 hover:bg-amber-600/30 text-amber-300 border border-amber-500/30 transition-colors">
<i class="fas fa-sort-amount-up mr-2"></i>
<span>Rerank</span>
</button>
<button @click="filterByTerm('whisper')"
class="group flex items-center justify-center rounded-xl px-4 py-3 text-sm font-semibold bg-gradient-to-r from-teal-600/80 to-teal-700/80 hover:from-teal-600 hover:to-teal-700 text-teal-100 border border-teal-500/30 hover:border-teal-400/50 transition-all duration-300 transform hover:scale-105 hover:shadow-lg hover:shadow-teal-500/25">
<i class="fas fa-headphones mr-2 group-hover:animate-pulse"></i>
class="flex items-center justify-center rounded-lg px-4 py-3 text-sm font-semibold bg-teal-600/20 hover:bg-teal-600/30 text-teal-300 border border-teal-500/30 transition-colors">
<i class="fas fa-headphones mr-2"></i>
<span>Whisper</span>
</button>
<button @click="filterByTerm('object-detection')"
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">
<i class="fas fa-eye mr-2 group-hover:animate-pulse"></i>
class="flex items-center justify-center rounded-lg px-4 py-3 text-sm font-semibold bg-red-600/20 hover:bg-red-600/30 text-red-300 border border-red-500/30 transition-colors">
<i class="fas fa-eye mr-2"></i>
<span>Vision</span>
</button>
</div>
@@ -156,16 +148,16 @@
<!-- Filter by Tags -->
<div x-show="allTags.length > 0">
<h3 class="text-lg font-semibold text-white mb-4 flex items-center">
<i class="fas fa-tags mr-3 text-pink-400"></i>
<h3 class="text-lg font-semibold text-[#E5E7EB] mb-4 flex items-center">
<i class="fas fa-tags mr-3 text-[#8B5CF6]"></i>
Browse by Tags
</h3>
<div class="max-h-32 overflow-y-auto scrollbar-thin scrollbar-thumb-gray-600/50 scrollbar-track-gray-800/50 pr-2">
<div class="max-h-32 overflow-y-auto pr-2">
<div class="flex flex-wrap gap-2">
<template x-for="tag in allTags" :key="tag">
<button @click="filterByTerm(tag)"
class="group inline-flex items-center text-xs px-3 py-2 rounded-full bg-gray-700/60 hover:bg-gray-600/80 text-gray-300 hover:text-white border border-gray-600/50 hover:border-gray-500/70 transition-all duration-200 ease-in-out transform hover:scale-105">
<i class="fas fa-tag text-xs mr-2 group-hover:animate-pulse"></i>
class="inline-flex items-center text-xs px-3 py-2 rounded bg-[#101827] hover:bg-[#101827]/80 text-[#94A3B8] hover:text-[#E5E7EB] border border-[#1E293B] transition-colors">
<i class="fas fa-tag text-xs mr-2"></i>
<span x-text="tag"></span>
</button>
</template>
@@ -432,8 +424,8 @@
<button @click="goToPage(currentPage - 1)"
:disabled="currentPage <= 1"
:class="currentPage <= 1 ? 'opacity-50 cursor-not-allowed' : ''"
class="group flex items-center justify-center h-12 w-12 bg-gray-700/80 hover:bg-indigo-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>
class="flex items-center justify-center h-12 w-12 bg-[#1E293B] hover:bg-indigo-600 text-[#94A3B8] hover:text-white rounded-lg transition-colors">
<i class="fas fa-chevron-left"></i>
</button>
<div class="text-gray-300 text-sm font-medium px-4">
<span class="text-gray-400">Page</span>

View File

@@ -12,39 +12,38 @@
<div class="container mx-auto px-4 py-8 flex-grow">
{{ if eq .P2PToken "" }}
<!-- P2P Disabled - Wizard Guide -->
<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-circle-nodes"></i>
<div class="bg-[#1E293B] border border-[#8B5CF6]/20 rounded-xl p-12">
<div class="text-center max-w-4xl mx-auto">
<div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-[#8B5CF6]/10 border border-[#8B5CF6]/20 mb-6">
<i class="text-[#8B5CF6] text-2xl fas fa-circle-nodes"></i>
</div>
<h2 class="text-3xl md:text-4xl font-bold text-[#E5E7EB] mb-6">
<h2 class="text-3xl md:text-4xl font-bold text-[#E5E7EB] mb-4">
<span class="bg-clip-text text-transparent bg-gradient-to-r from-[#38BDF8] to-[#8B5CF6]">
P2P Distribution Not Enabled
</span>
</h2>
<p class="text-xl text-[#94A3B8] mb-8 leading-relaxed">
<p class="text-xl text-[#94A3B8] mb-8">
Enable peer-to-peer distribution to scale your AI workloads across multiple devices. Share instances, shard models, and pool computational resources across your network.
</p>
<!-- Features Preview -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-10">
<div class="bg-[#101827] border border-[#38BDF8]/20 rounded-xl p-4">
<div class="w-10 h-10 bg-blue-500/20 rounded-lg flex items-center justify-center mx-auto mb-3">
<div class="bg-[#101827] border border-[#38BDF8]/20 rounded-lg p-4">
<div class="w-10 h-10 bg-blue-500/10 rounded-lg flex items-center justify-center mx-auto mb-3">
<i class="fas fa-network-wired text-[#38BDF8] text-xl"></i>
</div>
<h3 class="text-sm font-semibold text-[#E5E7EB] mb-2">Instance Federation</h3>
<p class="text-xs text-[#94A3B8]">Load balance across multiple instances</p>
</div>
<div class="bg-[#101827] border border-[#8B5CF6]/20 rounded-xl p-4">
<div class="w-10 h-10 bg-purple-500/20 rounded-lg flex items-center justify-center mx-auto mb-3">
<div class="bg-[#101827] border border-[#8B5CF6]/20 rounded-lg p-4">
<div class="w-10 h-10 bg-purple-500/10 rounded-lg flex items-center justify-center mx-auto mb-3">
<i class="fas fa-puzzle-piece text-[#8B5CF6] text-xl"></i>
</div>
<h3 class="text-sm font-semibold text-[#E5E7EB] mb-2">Model Sharding</h3>
<p class="text-xs text-[#94A3B8]">Split large models across workers</p>
</div>
<div class="bg-[#101827] border border-green-500/20 rounded-xl p-4">
<div class="w-10 h-10 bg-green-500/20 rounded-lg flex items-center justify-center mx-auto mb-3">
<div class="bg-[#101827] border border-green-500/20 rounded-lg p-4">
<div class="w-10 h-10 bg-green-500/10 rounded-lg flex items-center justify-center mx-auto mb-3">
<i class="fas fa-share-alt text-green-400 text-xl"></i>
</div>
<h3 class="text-sm font-semibold text-[#E5E7EB] mb-2">Resource Sharing</h3>
@@ -98,16 +97,16 @@
<div class="flex flex-wrap justify-center gap-4">
<a href="https://localai.io/features/distribute/" target="_blank"
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)]">
class="inline-flex items-center bg-[#8B5CF6] hover:bg-[#8B5CF6]/90 text-white py-3 px-6 rounded-lg font-semibold transition-colors">
<i class="fas fa-book mr-2"></i>
Documentation
<i class="fas fa-external-link-alt ml-2 text-sm opacity-70"></i>
<i class="fas fa-external-link-alt ml-2 text-sm"></i>
</a>
<a href="https://localai.io/basics/getting_started/" 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">
class="inline-flex items-center bg-[#1E293B] hover:bg-[#1E293B]/80 border border-[#8B5CF6]/20 text-[#E5E7EB] py-3 px-6 rounded-lg font-semibold transition-colors">
<i class="fas fa-graduation-cap mr-2"></i>
Getting Started
<i class="fas fa-external-link-alt ml-2 text-sm opacity-70"></i>
<i class="fas fa-external-link-alt ml-2 text-sm"></i>
</a>
</div>
</div>
@@ -134,21 +133,15 @@
</div>
<!-- How P2P Distribution Works -->
<div class="relative bg-gradient-to-br from-indigo-900/40 via-purple-900/30 to-blue-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-blue-500/20 to-purple-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">
<div class="bg-[#1E293B] border border-[#8B5CF6]/20 rounded-xl p-8 mb-12">
<div>
<div class="text-center mb-10">
<h2 class="text-3xl md:text-4xl font-bold text-white mb-4">
<h2 class="text-3xl md:text-4xl font-bold text-[#E5E7EB] mb-4">
<span class="bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-purple-400">
How P2P Distribution Works
</span>
</h2>
<p class="text-lg text-gray-300 max-w-3xl mx-auto">
<p class="text-lg text-[#94A3B8] max-w-3xl mx-auto">
LocalAI leverages cutting-edge peer-to-peer technologies to distribute AI workloads intelligently across your network
</p>
</div>
@@ -156,34 +149,34 @@
<!-- Key Features Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- Federation -->
<div class="bg-white/10 rounded-2xl p-6 backdrop-blur-sm border border-white/20 hover:bg-white/15 transition-all duration-300 group">
<div class="w-12 h-12 bg-blue-500/20 rounded-xl flex items-center justify-center mb-4 group-hover:scale-110 transition-transform duration-300">
<div class="bg-[#101827] rounded-xl p-6 border border-[#38BDF8]/20 transition-colors">
<div class="w-12 h-12 bg-blue-500/10 rounded-lg flex items-center justify-center mb-4">
<i class="fas fa-network-wired text-blue-400 text-xl"></i>
</div>
<h3 class="text-xl font-bold text-white mb-3">Instance Federation</h3>
<p class="text-gray-300 text-sm leading-relaxed">
<h3 class="text-xl font-bold text-[#E5E7EB] mb-3">Instance Federation</h3>
<p class="text-[#94A3B8] text-sm leading-relaxed">
Share complete LocalAI instances across your network for load balancing and redundancy. Perfect for scaling across multiple devices.
</p>
</div>
<!-- Model Sharding -->
<div class="bg-white/10 rounded-2xl p-6 backdrop-blur-sm border border-white/20 hover:bg-white/15 transition-all duration-300 group">
<div class="w-12 h-12 bg-purple-500/20 rounded-xl flex items-center justify-center mb-4 group-hover:scale-110 transition-transform duration-300">
<div class="bg-[#101827] rounded-xl p-6 border border-[#8B5CF6]/20 transition-colors">
<div class="w-12 h-12 bg-purple-500/10 rounded-lg flex items-center justify-center mb-4">
<i class="fas fa-puzzle-piece text-purple-400 text-xl"></i>
</div>
<h3 class="text-xl font-bold text-white mb-3">Model Sharding</h3>
<p class="text-gray-300 text-sm leading-relaxed">
<h3 class="text-xl font-bold text-[#E5E7EB] mb-3">Model Sharding</h3>
<p class="text-[#94A3B8] text-sm leading-relaxed">
Split large model weights across multiple workers. Currently supported with llama.cpp backends for efficient memory usage.
</p>
</div>
<!-- Resource Sharing -->
<div class="bg-white/10 rounded-2xl p-6 backdrop-blur-sm border border-white/20 hover:bg-white/15 transition-all duration-300 group">
<div class="w-12 h-12 bg-green-500/20 rounded-xl flex items-center justify-center mb-4 group-hover:scale-110 transition-transform duration-300">
<div class="bg-[#101827] rounded-xl p-6 border border-green-500/20 transition-colors">
<div class="w-12 h-12 bg-green-500/10 rounded-lg flex items-center justify-center mb-4">
<i class="fas fa-share-alt text-green-400 text-xl"></i>
</div>
<h3 class="text-xl font-bold text-white mb-3">Resource Sharing</h3>
<p class="text-gray-300 text-sm leading-relaxed">
<h3 class="text-xl font-bold text-[#E5E7EB] mb-3">Resource Sharing</h3>
<p class="text-[#94A3B8] text-sm leading-relaxed">
Pool computational resources from multiple devices, including your friends' machines, to handle larger workloads collaboratively.
</p>
</div>
@@ -220,7 +213,7 @@
</div>
<!-- Network Token Card -->
<div class="bg-gradient-to-r from-gray-800/90 to-gray-800/80 border border-gray-700/50 rounded-xl overflow-hidden shadow-xl mb-10 p-6 transition-all duration-300 hover:shadow-lg hover:shadow-blue-900/20 hover:border-blue-700/50">
<div class="bg-[#1E293B] border border-[#8B5CF6]/20 rounded-xl mb-10 p-6">
<div class="flex items-center mb-4">
<i class="fas fa-key text-yellow-400 text-xl mr-3"></i>
<h3 class="text-xl font-bold text-white">Network Token</h3>
@@ -237,7 +230,7 @@
<!-- Network Status Overview -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-10">
<!-- Federation Status -->
<div class="bg-gradient-to-br from-blue-800/60 to-blue-900/40 border border-blue-700/50 rounded-2xl p-6 shadow-xl transition-all duration-300 hover:shadow-blue-900/30">
<div class="bg-[#1E293B] border border-blue-500/20 rounded-xl p-6">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center">
<div class="w-12 h-12 bg-blue-500/20 rounded-xl flex items-center justify-center mr-3">
@@ -263,7 +256,7 @@
</div>
<!-- Workers Status -->
<div class="bg-gradient-to-br from-purple-800/60 to-purple-900/40 border border-purple-700/50 rounded-2xl p-6 shadow-xl transition-all duration-300 hover:shadow-purple-900/30">
<div class="bg-[#1E293B] border border-purple-500/20 rounded-xl p-6">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center">
<div class="w-12 h-12 bg-purple-500/20 rounded-xl flex items-center justify-center mr-3">
@@ -289,7 +282,7 @@
</div>
<!-- Network Token -->
<div class="bg-gradient-to-br from-yellow-800/60 to-yellow-900/40 border border-yellow-700/50 rounded-2xl p-6 shadow-xl transition-all duration-300 hover:shadow-yellow-900/30">
<div class="bg-[#1E293B] border border-yellow-500/20 rounded-xl p-6">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center">
<div class="w-12 h-12 bg-yellow-500/20 rounded-xl flex items-center justify-center mr-3">
@@ -312,7 +305,7 @@
</div>
<!-- Federation Box -->
<div class="bg-gradient-to-br from-gray-800/90 to-gray-800/80 border border-gray-700/50 rounded-2xl overflow-hidden shadow-xl mb-10 transition-all duration-300 hover:shadow-lg hover:shadow-blue-900/20 backdrop-blur-sm">
<div class="bg-[#1E293B] border border-[#8B5CF6]/20 rounded-xl mb-10">
<div class="p-8 border-b border-gray-700/50">
<div class="flex items-center justify-between mb-6">
<div class="flex items-center">
@@ -351,8 +344,8 @@
</template>
<template x-for="node in federationNodes" :key="node.id">
<div :class="node.isOnline ? 'border-green-400/50 hover:shadow-green-500/20' : 'border-red-400/50 hover:shadow-red-500/20'"
class="bg-gradient-to-br from-gray-800/90 to-gray-900/80 border rounded-xl p-5 shadow-xl transition-all duration-300 hover:border-opacity-100 backdrop-blur-sm">
<div :class="node.isOnline ? 'border-green-400/50' : 'border-red-400/50'"
class="bg-[#101827] border rounded-lg p-5 transition-colors">
<!-- Header with node icon and status -->
<div class="flex items-center justify-between mb-4">
<!-- Node info -->
@@ -366,8 +359,8 @@
</div>
</div>
<!-- Status badge -->
<div class="flex items-center bg-gray-900/50 rounded-full px-3 py-1.5 border border-gray-700/50">
<i :class="node.isOnline ? 'text-green-400' : 'text-red-400'" class="fas fa-circle animate-pulse mr-2 text-xs"></i>
<div class="flex items-center bg-[#101827] rounded-lg px-3 py-1.5 border border-[#1E293B]">
<i :class="node.isOnline ? 'text-green-400' : 'text-red-400'" class="fas fa-circle mr-2 text-xs"></i>
<span :class="node.isOnline ? 'text-green-400' : 'text-red-400'" class="text-xs font-medium" x-text="node.isOnline ? 'Online' : 'Offline'"></span>
</div>
</div>
@@ -468,7 +461,7 @@ docker run -ti --net host -e TOKEN="<span class="token">{{.P2PToken}}</span>" --
</div>
<!-- Workers Box -->
<div class="bg-gradient-to-br from-gray-800/90 to-gray-800/80 border border-gray-700/50 rounded-2xl overflow-hidden shadow-xl mb-10 transition-all duration-300 hover:shadow-lg hover:shadow-purple-900/20 backdrop-blur-sm">
<div class="bg-[#1E293B] border border-[#8B5CF6]/20 rounded-xl mb-10">
<div class="p-8 border-b border-gray-700/50">
<div class="flex items-center justify-between mb-6">
<div class="flex items-center">
@@ -507,8 +500,8 @@ docker run -ti --net host -e TOKEN="<span class="token">{{.P2PToken}}</span>" --
</template>
<template x-for="node in workerNodes" :key="node.id">
<div :class="node.isOnline ? 'border-green-400/50 hover:shadow-green-500/20' : 'border-red-400/50 hover:shadow-red-500/20'"
class="bg-gradient-to-br from-gray-800/90 to-gray-900/80 border rounded-xl p-5 shadow-xl transition-all duration-300 hover:border-opacity-100 backdrop-blur-sm">
<div :class="node.isOnline ? 'border-green-400/50' : 'border-red-400/50'"
class="bg-[#101827] border rounded-lg p-5 transition-colors">
<!-- Header with node icon and status -->
<div class="flex items-center justify-between mb-4">
<!-- Node info -->
@@ -522,8 +515,8 @@ docker run -ti --net host -e TOKEN="<span class="token">{{.P2PToken}}</span>" --
</div>
</div>
<!-- Status badge -->
<div class="flex items-center bg-gray-900/50 rounded-full px-3 py-1.5 border border-gray-700/50">
<i :class="node.isOnline ? 'text-green-400' : 'text-red-400'" class="fas fa-circle animate-pulse mr-2 text-xs"></i>
<div class="flex items-center bg-[#101827] rounded-lg px-3 py-1.5 border border-[#1E293B]">
<i :class="node.isOnline ? 'text-green-400' : 'text-red-400'" class="fas fa-circle mr-2 text-xs"></i>
<span :class="node.isOnline ? 'text-green-400' : 'text-red-400'" class="text-xs font-medium" x-text="node.isOnline ? 'Online' : 'Offline'"></span>
</div>
</div>

View File

@@ -1,12 +1,12 @@
<!-- Global Operations Status Bar -->
<div x-data="operationsStatus()" x-init="init()" x-show="operations.length > 0"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 transform -translate-y-4"
x-transition:enter-end="opacity-100 transform translate-y-0"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100 transform translate-y-0"
x-transition:leave-end="opacity-0 transform -translate-y-4"
class="sticky top-0 left-0 right-0 z-40 bg-[#1E293B]/95 backdrop-blur-sm shadow-2xl border-b border-[#38BDF8]/50">
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="sticky top-0 left-0 right-0 z-40 bg-[#1E293B]/95 backdrop-blur-sm border-b border-[#38BDF8]/50">
<div class="container mx-auto px-4 py-3">
<div class="flex items-center justify-between mb-2">
@@ -37,7 +37,7 @@
x-transition:leave-end="opacity-0 max-h-0"
class="space-y-2 overflow-y-auto max-h-96">
<template x-for="operation in operations" :key="operation.id">
<div class="bg-[#101827]/80 rounded-lg p-3 border border-[#1E293B] hover:border-[#38BDF8]/50 transition-all hover:shadow-[0_0_12px_rgba(56,189,248,0.15)]">
<div class="bg-[#101827]/80 rounded-lg p-3 border border-[#1E293B] hover:border-[#38BDF8]/50 transition-colors">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center space-x-3 flex-1 min-w-0">
<!-- Icon based on type -->
@@ -54,11 +54,11 @@
<div class="flex-1 min-w-0">
<div class="flex items-center space-x-2">
<span class="text-[#E5E7EB] font-medium text-sm truncate" x-text="operation.name"></span>
<span class="flex-shrink-0 text-xs px-2 py-0.5 rounded-full border"
<span class="flex-shrink-0 text-xs px-2 py-0.5 rounded border"
:class="{
'bg-[#38BDF8]/20 text-[#38BDF8] border-[#38BDF8]/30': !operation.isDeletion && !operation.isBackend,
'bg-[#8B5CF6]/20 text-[#8B5CF6] border-[#8B5CF6]/30': !operation.isDeletion && operation.isBackend,
'bg-red-500/20 text-red-300 border-red-500/30': operation.isDeletion
'bg-[#38BDF8]/10 text-[#38BDF8]': !operation.isDeletion && !operation.isBackend,
'bg-[#8B5CF6]/10 text-[#8B5CF6]': !operation.isDeletion && operation.isBackend,
'bg-red-500/10 text-red-300': operation.isDeletion
}"
x-text="operation.isBackend ? 'Backend' : 'Model'"></span>
</div>
@@ -105,10 +105,10 @@
<!-- Progress bar -->
<div class="w-full bg-[#101827] rounded-full h-2 overflow-hidden border border-[#1E293B]">
<div class="h-full rounded-full transition-all duration-300 ease-out"
<div class="h-full rounded-full transition-all duration-300"
:class="{
'bg-gradient-to-r from-[#38BDF8] to-[#8B5CF6]': !operation.isDeletion && !operation.isCancelled,
'bg-gradient-to-r from-red-500 to-red-600': operation.isDeletion || operation.isCancelled
'bg-[#38BDF8]': !operation.isDeletion && !operation.isCancelled,
'bg-red-500': operation.isDeletion || operation.isCancelled
}"
:style="'width: ' + operation.progress + '%'">
</div>

View File

@@ -9,7 +9,7 @@
<div class="container mx-auto px-4 py-8 flex-grow">
<!-- Hero Section -->
<div class="bg-[#1E293B] border border-[#38BDF8]/20 rounded-2xl shadow-2xl shadow-[#38BDF8]/10 p-8 mb-10">
<div class="bg-[#1E293B] border border-[#38BDF8]/20 rounded-xl p-8 mb-10">
<div class="max-w-4xl mx-auto text-center">
<h1 class="text-4xl md:text-5xl font-bold text-[#E5E7EB] mb-4">
<span class="bg-clip-text text-transparent bg-gradient-to-r from-[#38BDF8] to-[#8B5CF6]">
@@ -22,12 +22,12 @@
<!-- Talk Interface -->
<div class="max-w-3xl mx-auto">
<div class="bg-[#1E293B] border border-[#1E293B] rounded-xl overflow-hidden transition-all duration-300 shadow-lg">
<div class="bg-[#1E293B] border border-[#1E293B] rounded-xl overflow-hidden">
<!-- Talk Interface Body -->
<div class="p-6">
<!-- Recording Status -->
<div id="recording" class="bg-red-500/10 border border-red-500/30 rounded-lg p-4 mb-4 flex items-center space-x-3" style="display: none;">
<i class="fa-solid fa-microphone text-2xl text-red-400 animate-pulse"></i>
<i class="fa-solid fa-microphone text-2xl text-red-400"></i>
<span class="text-red-300 font-medium">Recording... press "Stop recording" to stop</span>
</div>
@@ -97,14 +97,13 @@
<!-- Buttons -->
<div class="flex items-center justify-between mt-8">
<button id="recordButton"
class="group flex items-center bg-red-500 hover:bg-red-600 text-white font-semibold py-2 px-6 rounded-lg transition duration-300 ease-in-out transform hover:scale-105 hover:shadow-[0_0_20px_rgba(239,68,68,0.4)]">
class="inline-flex items-center bg-red-500 hover:bg-red-600 text-white font-semibold py-2 px-6 rounded-lg transition-colors">
<i class="fas fa-microphone mr-2"></i>
<span>Talk</span>
<i class="fas fa-arrow-right opacity-0 group-hover:opacity-100 group-hover:translate-x-2 ml-2 transition-all duration-300"></i>
</button>
<a id="resetButton"
class="flex items-center text-[#38BDF8] hover:text-[#8B5CF6] transition duration-200"
class="flex items-center text-[#38BDF8] hover:text-[#8B5CF6] transition-colors"
href="#">
<i class="fas fa-rotate-right mr-2"></i>
<span>Reset conversation</span>

View File

@@ -10,7 +10,7 @@
<div class="container mx-auto px-4 py-8 flex-grow" x-data="{ component: 'menu' }">
<!-- Hero Section -->
<div class="bg-[#1E293B] border border-[#38BDF8]/20 rounded-2xl shadow-2xl shadow-[#38BDF8]/10 p-8 mb-6">
<div class="bg-[#1E293B] border border-[#38BDF8]/20 rounded-xl p-8 mb-6">
<div class="max-w-4xl mx-auto text-center">
<h1 class="text-4xl md:text-5xl font-bold text-[#E5E7EB] mb-4">
<span class="bg-clip-text text-transparent bg-gradient-to-r from-[#38BDF8] to-[#8B5CF6]">
@@ -20,23 +20,21 @@
<p class="text-xl text-[#94A3B8] mb-6">Create stunning images from text descriptions</p>
<div class="flex flex-wrap justify-center gap-4">
<a href="https://localai.io/features/image-generation/" target="_blank"
class="group flex items-center bg-[#38BDF8] hover:bg-[#38BDF8]/90 text-[#101827] font-semibold py-2 px-6 rounded-lg transition duration-300 ease-in-out transform hover:scale-105 hover:shadow-[0_0_20px_rgba(56,189,248,0.4)]">
class="inline-flex items-center bg-[#38BDF8] hover:bg-[#38BDF8]/90 text-[#101827] font-semibold py-2 px-6 rounded-lg transition-colors">
<i class="fas fa-book-reader mr-2"></i>
<span>Documentation</span>
<i class="fas fa-arrow-right opacity-0 group-hover:opacity-100 group-hover:translate-x-2 ml-2 transition-all duration-300"></i>
</a>
<a href="browse"
class="group flex items-center bg-[#8B5CF6] hover:bg-[#8B5CF6]/90 text-white font-semibold py-2 px-6 rounded-lg transition duration-300 ease-in-out transform hover:scale-105 hover:shadow-[0_0_20px_rgba(139,92,246,0.4)]">
class="inline-flex items-center bg-[#8B5CF6] hover:bg-[#8B5CF6]/90 text-white font-semibold py-2 px-6 rounded-lg transition-colors">
<i class="fas fa-images mr-2"></i>
<span>Gallery</span>
<i class="fas fa-arrow-right opacity-0 group-hover:opacity-100 group-hover:translate-x-2 ml-2 transition-all duration-300"></i>
</a>
</div>
</div>
</div>
<!-- Model Selection - Positioned between hero and generation form -->
<div class="bg-[#1E293B] border border-[#1E293B] rounded-xl p-5 mb-6 shadow-lg">
<div class="bg-[#1E293B] border border-[#1E293B] rounded-xl p-5 mb-6">
<div class="flex items-center">
<div class="text-lg font-medium text-[#38BDF8] mr-4">
<i class="fas fa-palette mr-2"></i>Select Model:
@@ -65,7 +63,7 @@
</div>
<!-- Image Generation Form -->
<div class="bg-[#1E293B] border border-[#1E293B] rounded-xl p-6 shadow-lg backdrop-blur-sm">
<div class="bg-[#1E293B] border border-[#1E293B] rounded-xl p-6">
<h2 class="text-2xl font-bold text-[#E5E7EB] mb-6">Generate an Image</h2>
<div class="relative">
@@ -110,7 +108,7 @@
<div class="mt-6">
<button
type="submit"
class="w-full bg-[#38BDF8] hover:bg-[#38BDF8]/90 text-[#101827] font-semibold py-3 px-6 rounded-lg transition duration-300 ease-in-out transform hover:scale-105 hover:shadow-[0_0_20px_rgba(56,189,248,0.4)] focus:outline-none focus:ring-2 focus:ring-[#38BDF8] focus:ring-opacity-50"
class="w-full bg-[#38BDF8] hover:bg-[#38BDF8]/90 text-[#101827] font-semibold py-3 px-6 rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-[#38BDF8] focus:ring-opacity-50"
>
<i class="fas fa-magic mr-2"></i>Generate Image
</button>

View File

@@ -9,7 +9,7 @@
{{template "views/partials/navbar" .}}
<div class="container mx-auto px-4 py-8 flex-grow">
<!-- Hero Section -->
<div class="bg-[#1E293B] border border-[#8B5CF6]/20 rounded-2xl shadow-2xl shadow-[#8B5CF6]/10 p-8 mb-10">
<div class="bg-[#1E293B] border border-[#8B5CF6]/20 rounded-xl p-8 mb-10">
<div class="max-w-4xl mx-auto text-center">
<h1 class="text-4xl md:text-5xl font-bold text-[#E5E7EB] mb-4">
<span class="bg-clip-text text-transparent bg-gradient-to-r from-[#8B5CF6] to-[#38BDF8]">
@@ -19,10 +19,9 @@
<p class="text-xl text-[#94A3B8] mb-6">Convert your text into natural-sounding speech</p>
<div class="flex flex-wrap justify-center gap-4">
<a href="https://localai.io/features/text-to-audio/" target="_blank"
class="group flex items-center bg-[#8B5CF6] hover:bg-[#8B5CF6]/90 text-white font-semibold py-2 px-6 rounded-lg transition duration-300 ease-in-out transform hover:scale-105 hover:shadow-[0_0_20px_rgba(139,92,246,0.4)]">
class="inline-flex items-center bg-[#8B5CF6] hover:bg-[#8B5CF6]/90 text-white font-semibold py-2 px-6 rounded-lg transition-colors">
<i class="fas fa-book-reader mr-2"></i>
<span>Documentation</span>
<i class="fas fa-arrow-right opacity-0 group-hover:opacity-100 group-hover:translate-x-2 ml-2 transition-all duration-300"></i>
</a>
</div>
</div>
@@ -30,7 +29,7 @@
<!-- TTS Interface -->
<div class="max-w-3xl mx-auto">
<div class="bg-[#1E293B] border border-[#1E293B] rounded-xl overflow-hidden transition-all duration-300 shadow-lg">
<div class="bg-[#1E293B] border border-[#1E293B] rounded-xl overflow-hidden">
<!-- Header with Model Selection -->
<div class="border-b border-[#1E293B] p-5">
<div class="flex flex-col sm:flex-row items-center justify-between gap-4">