mirror of
https://github.com/mudler/LocalAI.git
synced 2026-01-01 23:21:13 -06:00
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:
committed by
GitHub
parent
d1a0dd10e6
commit
cd7d384500
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
588
core/http/views/manage.html
Normal 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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user