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

+27 -19
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Knowledge Base — Nexus One AI</title>
<link rel="stylesheet" href="style.css?v=4">
<link rel="stylesheet" href="style.css?v=12">
<style>
/* ── Shared ───────────────────────────────────────────────── */
.kb-card { background:var(--navy2); border:1px solid var(--bdr); border-radius:14px; padding:28px; margin-bottom:28px; }
@@ -139,6 +139,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>
@@ -178,7 +179,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>
@@ -213,12 +214,13 @@
<!-- ── 1. Collections ── -->
<div class="kb-card">
<div class="kb-card-title">📚 Collections</div>
<div class="kb-card-title">Collections</div>
<div class="kb-card-sub">A collection is a named group of documents that can be queried together. Create one per topic, department, or project.</div>
<div class="kb-toolbar">
<button class="kb-btn primary" onclick="openCreateCollection()">+ New Collection</button>
<button class="kb-btn ghost" onclick="loadCollections()">↺ Refresh</button>
<button class="kb-btn ghost" onclick="loadCollections()">Refresh</button>
<input type="text" id="col-filter" placeholder="Filter collections…" oninput="renderCollections()" style="margin-left:auto;padding:8px 10px;border:1px solid var(--bdr);border-radius:7px;font-size:12.5px;font-family:inherit;min-width:200px">
</div>
<div class="col-grid" id="col-grid">
@@ -229,14 +231,14 @@
<!-- ── 2. Documents (shown when a collection is selected) ── -->
<div class="kb-card docs-section" id="docs-section">
<div class="docs-header">
<span class="back-btn" onclick="deselectCollection()">← All collections</span>
<span class="back-btn" onclick="deselectCollection()">All collections</span>
<h3 id="docs-col-name">Documents</h3>
<span id="docs-col-stats" style="font-size:12px;color:var(--lt)"></span>
</div>
<div class="upload-zone" id="upload-zone" onclick="document.getElementById('file-input').click()">
<input type="file" id="file-input" accept=".pdf,.txt,.md,.docx,.doc,.csv" multiple onchange="uploadFiles(this.files)">
<div class="uz-icon">📄</div>
<div class="uz-icon">Docs</div>
<div class="uz-label">Click or drag to upload documents</div>
<div class="uz-hint">.pdf · .docx · .txt · .md · .csv — max 200 MB each</div>
</div>
@@ -244,7 +246,7 @@
<div style="margin-top:20px;">
<div class="kb-toolbar">
<button class="kb-btn ghost" onclick="loadDocuments(selectedColId)">↺ Refresh</button>
<button class="kb-btn ghost" onclick="loadDocuments(selectedColId)">Refresh</button>
<span id="doc-auto-refresh" style="font-size:12px;color:var(--lt)"></span>
</div>
<div class="kb-table-wrap">
@@ -262,7 +264,7 @@
<!-- ── 3. Query Tester ── -->
<div class="kb-card">
<div class="kb-card-title">🔍 Query Tester</div>
<div class="kb-card-title">Query Tester</div>
<div class="kb-card-sub">Ask a question and retrieve the most relevant document chunks. Use this to test retrieval quality before connecting a model.</div>
<div class="query-opts">
@@ -307,7 +309,7 @@
<input type="text" id="col-desc" class="modal-input" placeholder="What documents will this contain?">
</div>
<div class="modal-field">
<label class="modal-label">Embedding Model</label>
<label class="modal-label">Embedding Model <span class="cezen-help" tabindex="0" data-tip="The model that converts document text into searchable vectors. Once documents are indexed with one embedding model, switching later means re-indexing the whole collection — pick one and stick with it per collection.">?</span></label>
<select id="col-embed" class="modal-select">
<option value="nomic-embed-text">nomic-embed-text (recommended)</option>
<option value="mxbai-embed-large">mxbai-embed-large</option>
@@ -411,13 +413,19 @@ function renderCollections() {
grid.innerHTML = '<div style="color:var(--lt);font-size:13px;padding:20px 0">No collections yet — create one to get started.</div>';
return;
}
grid.innerHTML = collectionsCache.map(c => `
const q = (document.getElementById('col-filter')?.value || '').trim().toLowerCase();
const filtered = q ? collectionsCache.filter(c => (c.name || '').toLowerCase().includes(q) || (c.description || '').toLowerCase().includes(q)) : collectionsCache;
if (!filtered.length) {
grid.innerHTML = '<div class="cezen-list-empty">No collections match your filter.</div>';
return;
}
grid.innerHTML = filtered.map(c => `
<div class="col-card ${selectedColId === c.id ? 'selected' : ''}" onclick="selectCollection(${c.id})">
<div class="col-card-name">📂 ${c.name}</div>
<div class="col-card-name"><span class="icon-svg icon-xs" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg></span> ${c.name}</div>
<div class="col-card-desc">${c.description || '<span style="color:var(--lt);font-style:italic">No description</span>'}</div>
<div class="col-card-meta">
<span>📄 ${c.doc_count} doc${c.doc_count !== 1 ? 's' : ''}</span>
<span>🧩 ${c.chunk_count.toLocaleString()} chunks</span>
<span>${c.doc_count} doc${c.doc_count !== 1 ? 's' : ''}</span>
<span><span class="icon-svg icon-xs" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 2 2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg></span> ${c.chunk_count.toLocaleString()} chunks</span>
<span>${fmt(c.created_at)}</span>
</div>
<div class="col-card-actions">
@@ -458,7 +466,7 @@ async function createCollection() {
const d = await res.json();
if (!res.ok) { errEl.textContent = d.detail || 'Failed to create'; errEl.classList.add('show'); return; }
toast(`✓ Collection "${name}" created`);
toast(`Collection "${name}" created`);
closeModals();
document.getElementById('col-name').value = '';
document.getElementById('col-desc').value = '';
@@ -528,7 +536,7 @@ async function loadDocuments(colId) {
<td><strong>${d.orig_name}</strong></td>
<td>${d.chunk_count.toLocaleString()}</td>
<td>${fmtBytes(d.size_bytes)}</td>
<td><span class="doc-status ${d.status}">${d.status}${d.error_msg ? ` <span title="${d.error_msg.replace(/"/g,'&quot;')}">⚠</span>` : ''}</span></td>
<td><span class="doc-status ${d.status}">${d.status}${d.error_msg ? ` <span title="${d.error_msg.replace(/"/g,'&quot;')}">Issue</span>` : ''}</span></td>
<td>${fmt(d.uploaded_at)}</td>
<td><button class="kb-btn danger" style="padding:5px 12px;font-size:12px" onclick="deleteDocument(${colId},${d.id},'${d.orig_name.replace(/'/g,"\\'")}')">Delete</button></td>
</tr>
@@ -537,7 +545,7 @@ async function loadDocuments(colId) {
// Auto-refresh while any doc is still processing
const hasPending = docs.some(d => d.status === 'pending' || d.status === 'processing');
if (hasPending) {
document.getElementById('doc-auto-refresh').textContent = '⟳ Auto-refreshing…';
document.getElementById('doc-auto-refresh').textContent = 'Auto-refreshing...';
startDocRefresh(colId);
} else {
document.getElementById('doc-auto-refresh').textContent = '';
@@ -583,7 +591,7 @@ async function uploadFiles(files) {
});
const d = await res.json();
if (!res.ok) throw new Error(d.detail || 'Upload failed');
toast(`✓ ${file.name} uploaded — indexing started`);
toast(`${file.name} uploaded - indexing started`);
} catch(e) {
toast(`${file.name}: ${e.message}`, true);
}
@@ -655,7 +663,7 @@ async function runQuery() {
<div class="result-card" style="background:var(--navy2);border:1px solid var(--bdr)">
<div class="result-meta">
<span class="result-score" style="color:var(--purple)">Score: ${(r.score * 100).toFixed(1)}%</span>
<span class="result-source">📄 ${r.source}</span>
<span class="result-source">${r.source}</span>
${r.page ? `<span>Page ${r.page}</span>` : ''}
<span>Chunk ${r.chunk + 1}</span>
<span style="margin-left:auto;font-weight:600;color:var(--ink)">#${i+1}</span>
@@ -681,6 +689,6 @@ document.addEventListener('keydown', e => { if(e.key==='Escape') closeModals();
loadCollections();
</script>
<script src="auth.js"></script>
<script src="branding.js"></script>
<script src="branding.js?v=3"></script>
</body>
</html>