Files
LocalAI/core/http/views/index.html
Ettore Di Giacinto cd7d384500 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>
2025-11-16 11:01:05 +01:00

341 lines
18 KiB
HTML

<!DOCTYPE html>
<html lang="en">
{{template "views/partials/head" .}}
<body class="bg-[#101827] text-[#E5E7EB]">
<div class="flex flex-col min-h-screen">
{{template "views/partials/navbar" .}}
<!-- 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 - 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-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>
<!-- 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-[#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-[#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>
</div>
</div>
{{ else }}
<!-- 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>
<!-- 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" }}
<option value="{{$cfg.Name}}" class="bg-[#1E293B] text-[#E5E7EB]">{{$cfg.Name}}</option>
{{ end }}
{{ end }}
{{ end }}
</select>
</div>
<!-- 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>
</div>
{{template "views/partials/footer" .}}
</div>
<script>
// Handle form submission - redirect to chat with message
function startChat(event) {
if (event) {
event.preventDefault();
}
// 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;
}
// Store message and files in localStorage for chat page to pick up
const chatData = {
message: message,
imageFiles: [],
audioFiles: [],
textFiles: []
};
// 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');
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);
})
),
...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);
})
)
];
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 {
chatData.textFiles.push(file);
}
});
// 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>
</html>