feat: Redesign explorer and models pages with react-ui theme (#8903)

feat: redesign explorer and models pages with react-ui theme

- Updated logo and branding to match LocalAI's current design
- Applied react-ui color scheme and CSS variables throughout
- Added grid/list view toggle for models page
- Implemented enhanced filter chips with active state highlighting
- Added sort options and improved pagination
- Redesigned explorer page cards and token display
- Modernized navbar styling with sticky positioning
- Improved modal design with inline actions
- Ensured mobile-responsive design maintained

Co-authored-by: localai-bot <localai-bot@noreply.github.com>
This commit is contained in:
LocalAI [bot]
2026-03-09 17:32:32 +01:00
committed by GitHub
parent a026277ab9
commit 74db732873
3 changed files with 577 additions and 652 deletions

View File

@@ -3,287 +3,179 @@
{{template "views/partials/head" .}}
<style>
body {
background-color: var(--color-bg-primary);
color: var(--color-text-primary);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.token {
word-break: break-all;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
position: relative;
}
.network-card {
background-color: var(--color-bg-secondary);
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
border: 1px solid var(--color-border-subtle);
transition: background-color 0.2s ease;
}
.network-card:hover {
background-color: var(--color-bg-tertiary);
}
.network-title {
font-size: 24px;
font-weight: bold;
margin-bottom: 10px;
color: var(--color-primary);
}
.network-token {
font-size: 14px;
font-style: italic;
color: var(--color-text-secondary);
margin-bottom: 10px;
word-break: break-word;
overflow-wrap: break-word;
white-space: pre-wrap;
}
.cluster {
margin-top: 10px;
background-color: var(--color-bg-tertiary);
padding: 10px;
border-radius: 6px;
border: 1px solid var(--color-border-subtle);
transition: background-color 0.3s ease;
}
.cluster:hover {
background-color: var(--color-bg-secondary);
}
.cluster-title {
font-size: 18px;
font-weight: bold;
color: var(--color-text-primary);
}
.form-container {
background-color: var(--color-bg-secondary);
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
border: 1px solid var(--color-border-subtle);
}
.form-control {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: var(--color-text-primary);
}
input[type="text"],
textarea {
width: 100%;
padding: 10px;
border-radius: 4px;
border: 1px solid var(--color-border-subtle);
background-color: var(--color-bg-primary);
color: var(--color-text-primary);
transition: border-color 0.3s ease, background-color 0.3s ease;
}
input[type="text"]:focus,
textarea:focus {
border-color: var(--color-primary);
background-color: var(--color-bg-tertiary);
}
button {
background-color: var(--color-primary);
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s ease;
}
button:hover {
background-color: var(--color-primary-hover);
}
.error {
color: var(--color-error);
margin-top: 5px;
}
.success {
color: var(--color-success);
margin-top: 5px;
}
.spinner {
display: inline-block;
width: 50px;
height: 50px;
border: 5px solid var(--color-border-subtle);
border-radius: 50%;
border-top-color: var(--color-primary);
animation: spin 1s linear infinite;
margin: 0 auto;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading-container {
text-align: center;
padding: 50px;
}
.warning-box {
border-radius: 5px;
}
.warning-box i {
margin-right: 10px;
}
.token-box {
background-color: var(--color-bg-tertiary);
padding: 10px;
border-radius: 4px;
margin-top: 10px;
position: relative;
cursor: pointer;
border: 1px solid var(--color-border-subtle);
}
.token-box:hover {
background-color: var(--color-bg-secondary);
}
.token-text {
overflow-wrap: break-word;
font-family: monospace;
}
.copy-icon {
position: absolute;
top: 10px;
right: 10px;
color: var(--color-text-primary);
}
</style>
<body class="bg-[var(--color-bg-primary)] text-[var(--color-text-primary)]">
<div class="app-layout">
<div class="app-layout no-sidebar">
{{template "views/partials/navbar_explorer" .}}
<main class="main-content">
<div class="main-content-inner" x-data="networkClusters()" x-init="init()">
<div class="animation-container">
<canvas id="networkCanvas"></canvas>
<div class="text-overlay">
<header class="text-center py-12">
<h1 class="hero-title">
<i class="fa-solid fa-circle-nodes mr-2"></i> Network Clusters Explorer
<h1 class="hero-title" style="background: var(--gradient-text); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;">
<i class="fa-solid fa-circle-nodes mr-2" style="-webkit-text-fill-color: var(--color-primary);"></i> Network Explorer
</h1>
<p class="mt-4 text-lg">
View the clusters and workers available in each network.
<a href="https://localai.io/features/distribute/" target="_blank">
<i class="fas fa-circle-info pr-2"></i>
<p class="hero-subtitle mt-2">
Explore clusters and workers across the federated network
<a href="https://localai.io/features/distribute/" target="_blank" class="inline-flex items-center ml-1 text-[var(--color-primary)] hover:text-[var(--color-primary-hover)] transition-colors">
<i class="fas fa-circle-info"></i>
</a>
</p>
</header>
</div>
</div>
<div class="container mx-auto px-4 flex-grow">
<!-- Warning Box -->
<div class="warning-box bg-[var(--color-warning-light)] border border-[var(--color-warning)]/30 text-[var(--color-text-primary)] mb-20 pt-5 pb-5 pr-5 pl-5 text-lg rounded-lg">
<i class="fa-solid fa-triangle-exclamation text-[var(--color-warning)]"></i><i class="fa-solid fa-flask text-[var(--color-warning)]"></i>
The explorer is a global, community-driven tool to share network tokens and view available clusters in the globe.
Anyone can use the tokens to offload computation and use the clusters available or share resources.
This is provided without any warranty. Use it at your own risk. We are not responsible for any potential harm or misuse. Sharing tokens globally allows anyone from the internet to use your instances.
Although the community will address bugs, this is experimental software and may be insecure to deploy on your hardware unless you take all necessary precautions.
</div>
<div class="flow-root">
<!-- Toggle button for showing/hiding the form -->
<button type="button" class="inline-flex items-center gap-1.5 text-xs text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] bg-transparent hover:bg-[var(--color-primary)]/10 border border-[var(--color-border-subtle)] hover:border-[var(--color-primary)]/30 rounded-md py-1.5 px-2.5 transition-colors float-right mb-2" @click="toggleForm()">
<i :class="showForm ? 'fa-solid fa-times' : 'fa-solid fa-plus'"></i>
<span x-text="showForm ? 'Close' : 'Add New Network'"></span>
</button>
</div>
<div class="container mx-auto px-4 py-6 flex-grow" style="max-width: 900px;">
<!-- Warning Box -->
<div class="card p-4 mb-6 border-l-4 border-l-[var(--color-warning)]">
<div class="flex items-start gap-3">
<div class="flex items-center gap-1 text-[var(--color-warning)] flex-shrink-0 mt-0.5">
<i class="fa-solid fa-triangle-exclamation"></i>
<i class="fa-solid fa-flask text-xs"></i>
</div>
<p class="text-xs text-[var(--color-text-secondary)] leading-relaxed">
The explorer is a community-driven tool to share network tokens and view available clusters.
Anyone can use tokens to offload computation or share resources.
<strong class="text-[var(--color-text-primary)]">Use at your own risk.</strong>
Sharing tokens globally allows anyone to use your instances. This is experimental software.
</p>
</div>
</div>
<!-- Add Network Button -->
<div class="flex justify-end mb-4">
<button type="button"
class="inline-flex items-center gap-1.5 text-xs text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] bg-[var(--color-bg-secondary)] hover:bg-[var(--color-primary-light)] border border-[var(--color-border-subtle)] hover:border-[var(--color-primary-border)] rounded-lg py-2 px-3 transition-colors"
@click="toggleForm()">
<i :class="showForm ? 'fa-solid fa-times' : 'fa-solid fa-plus'"></i>
<span x-text="showForm ? 'Close' : 'Add Network'"></span>
</button>
</div>
<!-- Form for adding a new network -->
<div class="form-container" x-show="showForm" @click.outside="showForm = false">
<h2 class="h2"><i class="fa-solid fa-plus"></i> Add New Network</h2>
<div class="form-control">
<label for="name">Network Name</label>
<input type="text" id="name" x-model="newNetwork.name" placeholder="Enter network name" class="input" />
<div x-show="showForm" x-transition @click.outside="showForm = false"
class="card p-5 mb-6">
<h2 class="text-sm font-semibold text-[var(--color-text-primary)] mb-4 flex items-center gap-2">
<i class="fa-solid fa-plus text-[var(--color-primary)]"></i> Add New Network
</h2>
<div class="space-y-4">
<div>
<label for="name" class="text-xs font-medium text-[var(--color-text-secondary)] mb-1 block">Network Name</label>
<input type="text" id="name" x-model="newNetwork.name" placeholder="Enter network name"
class="input w-full text-sm" />
</div>
<div>
<label for="description" class="text-xs font-medium text-[var(--color-text-secondary)] mb-1 block">Description</label>
<textarea id="description" x-model="newNetwork.description" placeholder="Enter description"
class="input w-full text-sm" rows="2"></textarea>
</div>
<div>
<label for="token" class="text-xs font-medium text-[var(--color-text-secondary)] mb-1 block">Token</label>
<textarea id="token" x-model="newNetwork.token" placeholder="Enter token"
class="input w-full text-sm font-mono" rows="2"></textarea>
</div>
<div class="flex items-center gap-3">
<button type="button" @click="addNetwork"
class="inline-flex items-center gap-1.5 text-xs text-white bg-[var(--color-primary)] hover:bg-[var(--color-primary-hover)] rounded-lg py-2 px-4 transition-colors">
<i class="fa-solid fa-plus"></i> Add Network
</button>
<p x-show="errorMessage" class="text-xs text-[var(--color-error)]" x-text="errorMessage"></p>
<p x-show="successMessage" class="text-xs text-[var(--color-success)]" x-text="successMessage"></p>
</div>
</div>
<div class="form-control">
<label for="description">Description</label>
<textarea id="description" x-model="newNetwork.description" placeholder="Enter description" class="input"></textarea>
</div>
<div class="form-control">
<label for="token">Token</label>
<textarea id="token" x-model="newNetwork.token" placeholder="Enter token" class="input"></textarea>
</div>
<button type="button" @click="addNetwork" class="inline-flex items-center gap-1.5 text-xs text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] bg-transparent hover:bg-[var(--color-primary)]/10 border border-[var(--color-border-subtle)] hover:border-[var(--color-primary)]/30 rounded-md py-1.5 px-2.5 transition-colors"><i class="fa-solid fa-plus"></i> <span>Add Network</span></button>
<template x-if="errorMessage">
<p class="error" x-text="errorMessage"></p>
</template>
<template x-if="successMessage">
<p class="success" x-text="successMessage"></p>
</template>
</div>
<!-- Loading Spinner -->
<template x-if="networks.length === 0 && !loadingComplete">
<div class="loading-container">
<div class="spinner"></div>
<p class="text-center mt-4">Loading networks...</p>
<div class="text-center py-16">
<svg class="animate-spin h-8 w-8 text-[var(--color-primary)] mx-auto mb-3" 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>
<p class="text-sm text-[var(--color-text-secondary)]">Loading networks...</p>
</div>
</template>
<template x-if="networks.length === 0 && loadingComplete">
<div class="loading-container">
<p class="text-center mt-4">No networks available with online workers</p>
<div class="text-center py-16">
<i class="fa-solid fa-circle-nodes text-3xl text-[var(--color-text-muted)] mb-3"></i>
<p class="text-sm text-[var(--color-text-secondary)]">No networks available with online workers</p>
</div>
</template>
<!-- Display Networks -->
<template x-for="network in networks" :key="network.name">
<div class="network-card">
<i class="fa-solid fa-circle-nodes mr-2"></i><span class="network-title font-bold mb-4 mt-1" x-text="network.name"></span>
<div class="token-box" @click="copyToken(network.token)">
<p class="text-lg font-bold mb-4 mt-1">
<i class="fa-solid fa-copy copy-icon"></i>
<i class="fa-solid fa-key mr-2"></i>Token (click to copy):
</p>
<span class="token-text" x-text="network.token"></span>
</div>
<div class="space-y-4">
<template x-for="network in networks" :key="network.name">
<div class="card overflow-hidden">
<!-- Network Header -->
<div class="p-4 border-b border-[var(--color-border-subtle)]">
<div class="flex items-center gap-2 mb-3">
<i class="fa-solid fa-circle-nodes text-[var(--color-primary)]"></i>
<span class="text-base font-semibold text-[var(--color-text-primary)]" x-text="network.name"></span>
</div>
<div class="cluster">
<p class="text-lg font-bold mb-4 mt-1"><i class="fa-solid fa-book mr-2"></i> Description</p>
<p x-text="network.description"></p>
</div>
<h2 class="h2">Available Clusters in this network</h2>
<template x-for="cluster in network.Clusters" :key="cluster.NetworkID + cluster.Type">
<div class="cluster">
<div class="cluster-title"></div>
<span class="inline-block bg-orange-500 text-white py-1 px-3 rounded-full text-xs" x-text="'Cluster Type: ' + cluster.Type">
</span>
<span class="inline-block bg-orange-500 text-white py-1 px-3 rounded-full text-xs" x-show="cluster.NetworkID" x-text="'Network ID: ' + (cluster.NetworkID || 'N/A')">
</span>
<span class="inline-block bg-blue-500 text-white py-1 px-3 rounded-full text-xs" x-text="'Number of Workers: ' + cluster.Workers.length">
</span>
<!-- Give commands and instructions to join the network -->
<span class="inline-block token-box text-white py-1 px-3 text-xs" x-show="cluster.Type == 'federated'" >
<p class="text-lg font-bold mb-4 mt-1">
<i class="fa-solid fa-copy copy-icon float-right"></i>
Command to connect (click to copy):
</p>
<code class="block bg-[var(--color-bg-primary)] text-[var(--color-warning)] p-4 rounded-lg break-words border border-[var(--color-border-subtle)]" @click="copyToken($el.textContent)" >
docker run -d --restart=always -e ADDRESS=":80" -e LOCALAI_P2P_NETWORK_ID=<span class="token" x-text="cluster.NetworkID"></span> -e LOCALAI_P2P_LOGLEVEL=debug --name local-ai -e TOKEN="<span class="token" x-text="network.token"></span>" --net host -ti localai/localai:master federated --debug
</code>
or via CLI:
<code class="block bg-[var(--color-bg-primary)] text-[var(--color-warning)] p-4 rounded-lg break-words border border-[var(--color-border-subtle)]" @click="copyToken($el.textContent)" >
ADDRESS=":80" LOCALAI_P2P_NETWORK_ID=<span class="token" x-text="cluster.NetworkID"></span> LOCALAI_P2P_LOGLEVEL=debug TOKEN="<span class="token" x-text="network.token"></span>" local-ai federated --debug
</code>
</span>
<!-- Token -->
<div class="bg-[var(--color-bg-primary)] rounded-lg p-3 cursor-pointer border border-[var(--color-border-subtle)] hover:border-[var(--color-primary-border)] transition-colors group"
@click="copyToken(network.token)">
<div class="flex items-center justify-between mb-1.5">
<span class="text-[10px] font-medium text-[var(--color-text-muted)] uppercase tracking-wider">
<i class="fa-solid fa-key mr-1"></i>Token (click to copy)
</span>
<i class="fa-solid fa-copy text-xs text-[var(--color-text-muted)] group-hover:text-[var(--color-primary)] transition-colors"></i>
</div>
<code class="text-xs text-[var(--color-text-secondary)] break-all font-mono leading-relaxed" x-text="network.token"></code>
</div>
</div>
</template>
</div>
</template>
<!-- Description -->
<div class="px-4 py-3 border-b border-[var(--color-border-subtle)]">
<p class="text-xs text-[var(--color-text-secondary)]" x-text="network.description"></p>
</div>
<!-- Clusters -->
<div class="p-4">
<h3 class="text-xs font-semibold text-[var(--color-text-muted)] uppercase tracking-wider mb-3">
Available Clusters
</h3>
<div class="space-y-3">
<template x-for="cluster in network.Clusters" :key="cluster.NetworkID + cluster.Type">
<div class="bg-[var(--color-bg-primary)] rounded-lg p-3 border border-[var(--color-border-subtle)]">
<!-- Cluster badges -->
<div class="flex flex-wrap gap-1.5 mb-3">
<span class="inline-flex items-center text-[10px] px-2 py-0.5 rounded-full bg-[var(--color-warning-light)] text-[var(--color-warning)] border border-[var(--color-warning)]/20"
x-text="'Type: ' + cluster.Type"></span>
<span x-show="cluster.NetworkID"
class="inline-flex items-center text-[10px] px-2 py-0.5 rounded-full bg-[var(--color-accent-light)] text-[var(--color-accent)] border border-[var(--color-accent)]/20"
x-text="'ID: ' + (cluster.NetworkID || 'N/A')"></span>
<span class="inline-flex items-center text-[10px] px-2 py-0.5 rounded-full bg-[var(--color-primary-light)] text-[var(--color-primary)] border border-[var(--color-primary)]/20"
x-text="cluster.Workers.length + ' workers'"></span>
</div>
<!-- Federated connect commands -->
<div x-show="cluster.Type == 'federated'" class="space-y-2">
<p class="text-[10px] font-medium text-[var(--color-text-muted)] uppercase tracking-wider">Connect via Docker:</p>
<div class="relative group/cmd">
<code class="block bg-[var(--color-bg-secondary)] text-[var(--color-warning)] p-3 rounded-lg text-xs break-all border border-[var(--color-border-subtle)] font-mono leading-relaxed cursor-pointer"
@click="copyToken($el.textContent)">docker run -d --restart=always -e ADDRESS=":80" -e LOCALAI_P2P_NETWORK_ID=<span x-text="cluster.NetworkID"></span> -e LOCALAI_P2P_LOGLEVEL=debug --name local-ai -e TOKEN="<span x-text="network.token"></span>" --net host -ti localai/localai:master federated --debug</code>
<i class="fa-solid fa-copy absolute top-2 right-2 text-[10px] text-[var(--color-text-muted)] group-hover/cmd:text-[var(--color-primary)] transition-colors"></i>
</div>
<p class="text-[10px] font-medium text-[var(--color-text-muted)] uppercase tracking-wider mt-2">Connect via CLI:</p>
<div class="relative group/cmd">
<code class="block bg-[var(--color-bg-secondary)] text-[var(--color-warning)] p-3 rounded-lg text-xs break-all border border-[var(--color-border-subtle)] font-mono leading-relaxed cursor-pointer"
@click="copyToken($el.textContent)">ADDRESS=":80" LOCALAI_P2P_NETWORK_ID=<span x-text="cluster.NetworkID"></span> LOCALAI_P2P_LOGLEVEL=debug TOKEN="<span x-text="network.token"></span>" local-ai federated --debug</code>
<i class="fa-solid fa-copy absolute top-2 right-2 text-[10px] text-[var(--color-text-muted)] group-hover/cmd:text-[var(--color-primary)] transition-colors"></i>
</div>
</div>
</div>
</template>
</div>
</div>
</div>
</template>
</div>
</div>
<script>
function networkClusters() {
return {
@@ -295,33 +187,27 @@
},
errorMessage: '',
successMessage: '',
showForm: false, // Form visibility state
loadingComplete: false, // To track if loading is complete
showForm: false,
loadingComplete: false,
toggleForm() {
this.showForm = !this.showForm;
console.log('Toggling form:', this.showForm);
},
fetchNetworks() {
console.log('Fetching networks...');
fetch('/networks')
.then(response => response.json())
.then(data => {
console.log('Data fetched successfully:', data);
this.networks = data;
this.loadingComplete = true; // Set loading complete
this.loadingComplete = true;
})
.catch(error => {
console.error('Error fetching networks:', error);
this.loadingComplete = true; // Ensure spinner is hidden if error occurs
this.loadingComplete = true;
});
},
addNetwork() {
this.errorMessage = '';
this.successMessage = '';
console.log('Adding new network:', this.newNetwork);
// Validate input
if (!this.newNetwork.name || !this.newNetwork.description || !this.newNetwork.token) {
this.errorMessage = 'All fields are required.';
return;
@@ -329,9 +215,7 @@
fetch('/network/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(this.newNetwork)
})
.then(response => {
@@ -341,35 +225,32 @@
return response.json();
})
.then(data => {
console.log('Network added successfully:', data);
this.successMessage = 'Network added successfully!';
this.fetchNetworks(); // Refresh the networks list
this.newNetwork = { name: '', description: '', token: '' }; // Clear form
this.fetchNetworks();
this.newNetwork = { name: '', description: '', token: '' };
})
.catch(error => {
console.error('Error adding network:', error);
this.errorMessage = 'Failed to add network. Please try again.'
if (error.error) {
this.errorMessage += " Error : " + error.error;
this.errorMessage += " Error: " + error.error;
}
});
},
copyToken(token) {
navigator.clipboard.writeText(token)
.then(() => {
console.log('Text copied to clipboard:', token);
alert('Text copied to clipboard!');
})
.catch(err => {
console.error('Failed to copy token:', err);
});
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(token)
.then(() => alert('Copied to clipboard!'))
.catch(() => fallbackCopy(token));
} else {
fallbackCopy(token);
}
},
init() {
console.log('Initializing Alpine component...');
this.fetchNetworks();
setInterval(() => {
this.fetchNetworks();
}, 5000); // Refresh every 5 seconds
}, 5000);
}
}
}
@@ -383,4 +264,4 @@
</body>
</html>
</html>

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,49 +1,48 @@
<nav class="bg-[var(--color-bg-secondary)] shadow-lg border-b border-[var(--color-border-subtle)]">
<div class="container mx-auto px-4 py-3">
<nav class="bg-[var(--color-bg-secondary)] border-b border-[var(--color-border-subtle)]" style="position: sticky; top: 0; z-index: 40;">
<div class="container mx-auto px-4 py-2.5">
<div class="flex items-center justify-between">
<div class="flex items-center">
<!-- Logo Image -->
<a href="./" class="flex items-center group">
<img src="static/logo_horizontal.png"
alt="LocalAI Logo"
class="h-10 mr-3 rounded-lg border border-[var(--color-primary-border)] shadow-md transition-all duration-300 group-hover:shadow-[var(--color-primary)]/20 group-hover:border-[var(--color-primary)]/50">
<img src="static/logo_horizontal.png"
alt="LocalAI Logo"
class="h-8 rounded-lg border border-[var(--color-border-subtle)] transition-all duration-200 group-hover:border-[var(--color-primary-border)]">
</a>
</div>
<!-- Menu button for small screens -->
<div class="lg:hidden">
<button id="menu-toggle" class="text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] focus:outline-none p-2 rounded-lg transition duration-300 ease-in-out hover:bg-[var(--color-bg-primary)]">
<i class="fas fa-bars fa-lg"></i>
<button id="menu-toggle" class="text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] p-2 rounded-lg transition-colors hover:bg-[var(--color-bg-primary)]">
<i class="fas fa-bars"></i>
</button>
</div>
<!-- Navigation links -->
<div class="hidden lg:flex lg:items-center lg:justify-end lg:space-x-1">
<a href="./" class="text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] px-3 py-2 rounded-lg transition duration-300 ease-in-out hover:bg-[var(--color-bg-primary)] flex items-center">
<i class="fas fa-home text-[var(--color-primary)] mr-2"></i>Home
<div class="hidden lg:flex lg:items-center lg:gap-1">
<a href="./" class="text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] px-3 py-1.5 rounded-lg text-sm transition-colors hover:bg-[var(--color-bg-primary)] flex items-center gap-2">
<i class="fas fa-home text-[var(--color-primary)] text-xs"></i>Home
</a>
<a href="https://localai.io" target="_blank" class="text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] px-3 py-2 rounded-lg transition duration-300 ease-in-out hover:bg-[var(--color-bg-primary)] flex items-center group">
<i class="fas fa-book-reader text-[var(--color-primary)] mr-2"></i>Documentation
<i class="fas fa-external-link-alt text-xs ml-1 opacity-70 group-hover:opacity-100 transition-opacity"></i>
<a href="https://localai.io" target="_blank" class="text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] px-3 py-1.5 rounded-lg text-sm transition-colors hover:bg-[var(--color-bg-primary)] flex items-center gap-2 group">
<i class="fas fa-book-reader text-[var(--color-primary)] text-xs"></i>Docs
<i class="fas fa-external-link-alt text-[10px] opacity-50 group-hover:opacity-100 transition-opacity"></i>
</a>
<a href="https://models.localai.io/" class="text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] px-3 py-2 rounded-lg transition duration-300 ease-in-out hover:bg-[var(--color-bg-primary)] flex items-center">
<i class="fas fa-brain text-[var(--color-primary)] mr-2"></i>Models
<a href="https://models.localai.io/" class="text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] px-3 py-1.5 rounded-lg text-sm transition-colors hover:bg-[var(--color-bg-primary)] flex items-center gap-2">
<i class="fas fa-brain text-[var(--color-primary)] text-xs"></i>Models
</a>
</div>
</div>
<!-- Collapsible menu for small screens -->
<div class="hidden lg:hidden" id="mobile-menu">
<div class="pt-3 pb-2 space-y-1 border-t border-[var(--color-border-subtle)] mt-2">
<a href="./" class="block text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-primary)] px-3 py-2 rounded-lg transition duration-300 ease-in-out flex items-center">
<i class="fas fa-home text-[var(--color-primary)] mr-3 w-5 text-center"></i>Home
<div class="pt-2 pb-1 space-y-0.5 border-t border-[var(--color-border-subtle)] mt-2">
<a href="./" class="block text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-primary)] px-3 py-2 rounded-lg text-sm transition-colors flex items-center gap-2">
<i class="fas fa-home text-[var(--color-primary)] text-xs w-4 text-center"></i>Home
</a>
<a href="https://localai.io" target="_blank" class="block text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-primary)] px-3 py-2 rounded-lg transition duration-300 ease-in-out flex items-center">
<i class="fas fa-book-reader text-[var(--color-primary)] mr-3 w-5 text-center"></i>Documentation
<i class="fas fa-external-link-alt text-xs ml-1 opacity-70"></i>
<a href="https://localai.io" target="_blank" class="block text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-primary)] px-3 py-2 rounded-lg text-sm transition-colors flex items-center gap-2">
<i class="fas fa-book-reader text-[var(--color-primary)] text-xs w-4 text-center"></i>Docs
<i class="fas fa-external-link-alt text-[10px] opacity-50"></i>
</a>
<a href="https://models.localai.io/" class="block text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-primary)] px-3 py-2 rounded-lg transition duration-300 ease-in-out flex items-center">
<i class="fas fa-brain text-[var(--color-primary)] mr-3 w-5 text-center"></i>Models
<a href="https://models.localai.io/" class="block text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-primary)] px-3 py-2 rounded-lg text-sm transition-colors flex items-center gap-2">
<i class="fas fa-brain text-[var(--color-primary)] text-xs w-4 text-center"></i>Models
</a>
</div>
</div>
@@ -51,12 +50,10 @@
</nav>
<script>
// JavaScript to toggle the mobile menu with animation
document.getElementById('menu-toggle').addEventListener('click', function () {
var mobileMenu = document.getElementById('mobile-menu');
if (mobileMenu.classList.contains('hidden')) {
mobileMenu.classList.remove('hidden');
// Use setTimeout to create a mild animation effect
setTimeout(function() {
mobileMenu.classList.add('opacity-100');
mobileMenu.classList.remove('opacity-0');
@@ -64,11 +61,9 @@
} else {
mobileMenu.classList.add('opacity-0');
mobileMenu.classList.remove('opacity-100');
// Wait for transition to finish before hiding
setTimeout(function() {
mobileMenu.classList.add('hidden');
}, 300);
}
});
</script>