Sync cezen-portal/ from live working copy (V1.0 GA polish + search-icon ordering fix)

Brings the ISO-packaged portal copy current with the live cezen-portal/
working directory, which had drifted across the entire V1.0 GA polish
initiative (Phases 1-10) plus this session's topnav icon-order bugfix.

- 52 existing files updated, 2 new pages added (about.html, health.html)
- cgit/cezen-portal/ is now byte-identical to cezen-portal/
- Backend files (rag_ingest.py, cezen_license.py) already in sync;
  main.py intentionally left as-is (cgit is ahead there with
  workstation-category commercial-fit logic not yet backported to the
  live working copy)

Next: rebuild the ISO (bash autoinstall/build-iso.sh) to bake this in.
This commit is contained in:
Jino Jose committed 2026-07-09 11:11:38 +05:30
1 parent 7540da04a8
commit b3eec34158
57 files changed
+2832 -1354

No files matched your search

+40 -18
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Model Catalog — Nexus One AI</title>
<link rel="stylesheet" href="style.css?v=4">
<link rel="stylesheet" href="style.css?v=12">
<style>
.mm-tabs { display:flex; gap:4px; margin-bottom:24px; background:rgba(255,255,255,.03); border:1px solid var(--bdr); border-radius:10px; padding:4px; width:fit-content; }
.mm-tab { padding:7px 18px; border-radius:7px; font-size:13px; font-weight:600; cursor:pointer; color:var(--lt); border:none; background:none; font-family:inherit; transition:all .15s; }
@@ -126,6 +126,7 @@
<a href="dashboard.html">Dashboard</a>
<a href="analytics.html">Usage Analytics</a>
<a href="audit.html">Governance & Audit</a>
<a href="health.html">Health Center</a>
<a href="feedback.html">Feedback &amp; Ratings</a>
<span class="nav-drop-cat">MANAGE /</span>
<a href="users.html">Users</a>
@@ -165,7 +166,7 @@
</div>
</div>
</nav>
<a href="notifications.html" style="position:relative">🔔</a>
<a href="notifications.html" class="nav-icon-btn" style="position:relative" aria-label="Notifications"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M15 17h5l-1.4-1.4A2 2 0 0 1 18 14.2V11a6 6 0 1 0-12 0v3.2a2 2 0 0 1-.6 1.4L4 17h5"/><path d="M10 17a2 2 0 0 0 4 0"/></svg></span></a>
<span class="badge" data-brand="tier">Basic Tier</span>
<div id="nav-org-logo" class="nav-org-logo"></div>
</header>
@@ -183,11 +184,12 @@
<div class="mm-tabs">
<button class="mm-tab active" onclick="switchTab('installed', this)">Installed</button>
<button class="mm-tab" onclick="switchTab('available', this)">Pull from Registry</button>
<button class="mm-tab" onclick="switchTab('upload', this)">📁 Upload / Offline</button>
<button class="mm-tab" onclick="switchTab('upload', this)">Upload / Offline</button>
</div>
<!-- INSTALLED -->
<div id="tab-installed">
<input type="text" id="installed-filter" class="mm-filter-input" placeholder="Filter installed models by name…" oninput="filterInstalled()" style="width:100%;max-width:340px;margin-bottom:14px;padding:9px 12px;border:1px solid var(--bdr);border-radius:8px;font-size:13px;font-family:inherit">
<div class="mm-section-wrap" id="installed-list">
<div style="padding:24px;color:var(--lt)">Loading installed models…</div>
</div>
@@ -221,7 +223,7 @@
<div class="upload-zone" id="upload-zone" onclick="document.getElementById('gguf-file').click()">
<input type="file" id="gguf-file" accept=".gguf" onchange="onFileSelected(this)">
<div class="upload-zone-icon">📦</div>
<div class="upload-zone-icon">GGUF</div>
<div class="upload-zone-label">Click to select a .gguf file</div>
<div class="upload-zone-hint">Or drag and drop here &nbsp;·&nbsp; Files can be multi-GB — allow time to transfer</div>
</div>
@@ -274,7 +276,7 @@
</div>
<footer>
<p>Nexus One AI &nbsp;·&nbsp; Powered by Cezen &nbsp;·&nbsp; Basic Tier</p>
<p>Nexus One AI &nbsp;·&nbsp; Powered by Cezen &nbsp;·&nbsp; <span data-brand="tier">Basic Tier</span></p>
</footer>
<script>
@@ -290,6 +292,11 @@ const MOCK_INSTALLED = [
];
let installedNames = new Set();
let installedModels = [];
function escHtmlLocal(s) {
return (s || '').replace(/[&<>"']/g, c => ({ '&':'&amp;', '<':'&lt;', '>':'&gt;', '"':'&quot;', "'":'&#39;' }[c]));
}
function switchTab(name, btn) {
document.querySelectorAll('.mm-tab').forEach(t => t.classList.remove('active'));
@@ -322,12 +329,27 @@ async function loadInstalled() {
data = { models: MOCK_INSTALLED, installed_names: MOCK_INSTALLED.map(m=>m.name) };
}
installedNames = new Set(data.installed_names || []);
installedModels = data.models || [];
renderInstalledList(installedModels);
}
function filterInstalled() {
const q = (document.getElementById('installed-filter').value || '').trim().toLowerCase();
const filtered = q ? installedModels.filter(m => (m.name || '').toLowerCase().includes(q)) : installedModels;
renderInstalledList(filtered, q);
}
function renderInstalledList(models, activeQuery) {
const el = document.getElementById('installed-list');
if (!data.models || !data.models.length) {
el.innerHTML = '<div style="padding:24px;color:var(--lt)">No models installed yet. Go to "Available to Pull" to download your first model.</div>';
if (!installedModels.length) {
el.innerHTML = '<div class="cezen-list-empty">No models installed yet &mdash; <a onclick="document.querySelector(\'.mm-tab[onclick*=available]\').click()">pull your first model from the registry</a>.</div>';
return;
}
el.innerHTML = data.models.map(m => {
if (!models.length) {
el.innerHTML = `<div class="cezen-list-empty">No installed models match "${escHtmlLocal(activeQuery || '')}".</div>`;
return;
}
el.innerHTML = models.map(m => {
const statusColor = m.status==='loaded' ? '#15803D' : '#6B7280';
const statusLabel = m.status==='loaded' ? '● loaded' : '○ idle';
const qLevel = m.details?.quantization_level || '';
@@ -354,12 +376,12 @@ async function loadAvailable() {
const grid = document.getElementById('available-grid');
grid.innerHTML = models.map(m => `
<div class="mm-card ${m.installed ? 'installed' : ''}">
${m.installed ? '<div class="installed-badge">✓ Installed</div>' : ''}
${m.installed ? '<div class="installed-badge">Installed</div>' : ''}
<div class="mm-card-name">${m.label}</div>
<div class="mm-card-use">${m.use}</div>
<div class="mm-card-meta">
<span class="mm-tag size">💾 ${m.size_gb} GB download</span>
<span class="mm-tag vram">🎮 ~${m.vram_gb} GB VRAM</span>
<span class="mm-tag size">${m.size_gb} GB download</span>
<span class="mm-tag vram">~${m.vram_gb} GB VRAM</span>
</div>
<div class="vram-bar-wrap"><div class="vram-bar" style="width:${Math.round(m.vram_gb/96*100)}%"></div></div>
<div style="font-size:10px;color:var(--lt);margin:4px 0 12px;text-align:right">${m.vram_gb} of 96 GB VRAM</div>
@@ -384,7 +406,7 @@ async function pullModel(name, btn) {
body: JSON.stringify({model: name})
});
const data = await res.json();
notice.textContent = `⏳ ${data.message}`;
notice.textContent = `Processing: ${data.message}`;
notice.classList.add('show');
setTimeout(() => { notice.classList.remove('show'); loadInstalled(); }, 8000);
} catch(e) {
@@ -427,7 +449,7 @@ function onFileSelected(input) {
selectedFile = input.files[0];
if (!selectedFile) return;
const info = document.getElementById('file-selected-info');
info.textContent = `📦 ${selectedFile.name} (${(selectedFile.size / 1e9).toFixed(2)} GB)`;
info.textContent = `${selectedFile.name} (${(selectedFile.size / 1e9).toFixed(2)} GB)`;
info.classList.add('show');
const nameInput = document.getElementById('upload-model-name');
if (!nameInput.value)
@@ -484,7 +506,7 @@ function doUpload() {
xhr.addEventListener('load', async () => {
prog.classList.remove('show');
if (xhr.status >= 200 && xhr.status < 300) {
showUploadMsg(`✓ File uploaded. Registering "${modelName}" with Ollama…`, 'ok');
showUploadMsg(`File uploaded. Registering "${modelName}" with Ollama...`, 'ok');
btn.disabled = true;
try {
const res = await fetch(`${_API}/models/load`, {
@@ -493,7 +515,7 @@ function doUpload() {
body: JSON.stringify({ filename: selectedFile.name, model_name: modelName })
});
if (res.ok) {
showUploadMsg(`✓ Model "${modelName}" is ready to use!`, 'ok');
showUploadMsg(`Model "${modelName}" is ready to use.`, 'ok');
loadInstalled(); loadUploads();
} else {
const d = await res.json().catch(()=>({}));
@@ -535,8 +557,8 @@ async function loadUploads() {
<div class="uf-row">
<div class="uf-name">${f.filename}</div>
<div class="uf-size">${f.size_gb} GB</div>
<button class="uf-load-btn" onclick="openLoadModal('${f.filename}')">Register →</button>
<button class="uf-del-btn" onclick="deleteUpload('${f.filename}', this)">✕</button>
<button class="uf-load-btn" onclick="openLoadModal('${f.filename}')">Register</button>
<button class="uf-del-btn" onclick="deleteUpload('${f.filename}', this)">Delete</button>
</div>
`).join('');
} catch {
@@ -592,6 +614,6 @@ document.getElementById('modal-load')?.addEventListener('click', e => {
loadInstalled();
</script>
<script src="auth.js"></script>
<script src="branding.js"></script>
<script src="branding.js?v=3"></script>
</body>
</html>