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

+116 -59
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document Intelligence — Nexus One AI</title>
<link rel="stylesheet" href="style.css?v=4">
<link rel="stylesheet" href="style.css?v=12">
<style>
/* ── Upload zone ── */
.di-upload-zone {
@@ -24,7 +24,13 @@
.di-upload-zone input[type="file"] {
position: absolute; inset: 0; opacity: 0; cursor: pointer; width: 100%; height: 100%;
}
.di-upload-icon { font-size: 36px; margin-bottom: 10px; }
.di-upload-icon {
width: 36px;
height: 36px;
margin: 0 auto 10px;
color: var(--text-secondary);
}
.di-upload-icon svg { width: 100%; height: 100%; }
.di-upload-title { font-size: 16px; font-weight: 700; color: var(--ink); margin-bottom: 4px; }
.di-upload-sub { font-size: 12px; color: var(--lt); }
.di-file-types { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin-top: 12px; }
@@ -47,7 +53,8 @@
margin-top: 10px;
}
.di-file-bar.show { display: flex; }
.di-file-icon { font-size: 22px; }
.di-file-icon { width: 22px; height: 22px; color: var(--text-secondary); }
.di-file-icon svg { width: 100%; height: 100%; }
.di-file-name { font-weight: 600; font-size: 13px; color: var(--ink); flex: 1; }
.di-file-size { font-size: 12px; color: var(--lt); }
.di-file-clear { background: none; border: none; cursor: pointer; font-size: 16px; color: var(--lt); padding: 0; }
@@ -69,7 +76,14 @@
font-weight: 600; color: var(--med); text-align: center; transition: .15s;
line-height: 1.3;
}
.di-mode-btn .di-mode-icon { font-size: 20px; display: block; margin-bottom: 6px; }
.di-mode-btn .di-mode-icon {
width: 20px;
height: 20px;
display: block;
margin: 0 auto 6px;
color: currentColor;
}
.di-mode-btn .di-mode-icon svg { width: 100%; height: 100%; }
.di-mode-btn.active { border-color: var(--purple); background:rgba(124,58,237,.06); color: var(--purple); }
.di-mode-btn:hover:not(.active) { border-color: var(--med); }
@@ -114,7 +128,15 @@
text-align: center;
}
.di-progress.show { display: block; }
.di-spinner { font-size: 30px; animation: spin 1.2s linear infinite; display: inline-block; margin-bottom: 10px; }
.di-spinner {
width: 30px;
height: 30px;
animation: spin 1.2s linear infinite;
display: inline-block;
margin-bottom: 10px;
color: var(--brand);
}
.di-spinner svg { width: 100%; height: 100%; }
@keyframes spin { to { transform: rotate(360deg); } }
.di-progress-label { font-size: 14px; color: var(--med); font-weight: 600; }
.di-progress-sub { font-size: 12px; color: var(--lt); margin-top: 4px; }
@@ -189,7 +211,8 @@
.di-check-item.pass { border-color: rgba(22,163,74,.25); background: rgba(22,163,74,.04); }
.di-check-item.fail { border-color: rgba(220,38,38,.25); background: rgba(220,38,38,.04); }
.di-check-item.missing { border-color: rgba(217,119,6,.25); background: rgba(217,119,6,.04); }
.di-check-icon { font-size: 18px; flex-shrink: 0; margin-top: 1px; }
.di-check-icon { width: 18px; height: 18px; flex-shrink: 0; margin-top: 1px; color: currentColor; }
.di-check-icon svg { width: 100%; height: 100%; }
.di-check-body { flex: 1; }
.di-check-title { font-size: 13px; font-weight: 600; color: var(--ink); margin-bottom: 2px; }
.di-check-note { font-size: 12px; color: var(--lt); }
@@ -294,6 +317,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>
@@ -333,7 +357,11 @@
</div>
</div>
</nav>
<a href="notifications.html" style="position:relative">🔔</a>
<a href="notifications.html" class="nav-icon-btn" style="position:relative" title="Notifications">
<span class="icon-svg icon-md" aria-hidden="true">
<svg viewBox="0 0 24 24"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
</span>
</a>
<span class="badge" data-brand="tier">Basic Tier</span>
<div id="nav-org-logo" class="nav-org-logo"></div>
</header>
@@ -350,7 +378,7 @@
<div class="di-upload-zone" id="upload-zone">
<input type="file" id="file-input"
accept=".pdf,.docx,.doc,.txt,.md,.png,.jpg,.jpeg,.webp,.bmp">
<div class="di-upload-icon">📄</div>
<div class="di-upload-icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg></div>
<div class="di-upload-title">Drop your file here or click to browse</div>
<div class="di-upload-sub">Supports PDF, Word, images, and plain text — up to 20 MB</div>
<div class="di-file-types">
@@ -363,26 +391,26 @@
<!-- Selected file bar -->
<div class="di-file-bar" id="file-bar">
<span class="di-file-icon" id="file-icon">📄</span>
<span class="di-file-icon icon-svg" id="file-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg></span>
<span class="di-file-name" id="file-name">—</span>
<span class="di-file-size" id="file-size"></span>
<button class="di-file-clear" onclick="clearFile()" title="Remove file">✕</button>
<button class="di-file-clear" onclick="clearFile()" title="Remove file"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M6 6l12 12M18 6 6 18"/></svg></span></button>
</div>
<!-- Second file (comparison mode) -->
<div class="di-compare-zone" id="compare-zone">
<div class="di-compare-label">📎 Second Document (to compare against)</div>
<div class="di-compare-label"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M21.44 11.05 12.25 20.24a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 1 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.82-2.82l8.49-8.48"/></svg></span><span>Second Document (to compare against)</span></span></div>
<div class="di-upload-zone" id="upload-zone-b" style="padding:24px 20px">
<input type="file" id="file-input-b"
accept=".pdf,.docx,.doc,.txt,.md,.png,.jpg,.jpeg,.webp,.bmp">
<div class="di-upload-icon" style="font-size:28px">📄</div>
<div class="di-upload-icon icon-svg" style="width:28px;height:28px" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg></div>
<div class="di-upload-title" style="font-size:14px">Drop second document here</div>
</div>
<div class="di-file-bar" id="file-bar-b">
<span class="di-file-icon" id="file-icon-b">📄</span>
<span class="di-file-icon icon-svg" id="file-icon-b" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg></span>
<span class="di-file-name" id="file-name-b">—</span>
<span class="di-file-size" id="file-size-b"></span>
<button class="di-file-clear" onclick="clearFileB()" title="Remove file">✕</button>
<button class="di-file-clear" onclick="clearFileB()" title="Remove file"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M6 6l12 12M18 6 6 18"/></svg></span></button>
</div>
</div>
@@ -392,36 +420,36 @@
<div class="di-mode-grid" id="mode-grid">
<div class="di-mode-section">GENERAL</div>
<button class="di-mode-btn active" data-mode="summarise" onclick="setMode('summarise',this)">
<span class="di-mode-icon">📝</span>Summarise
<span class="di-mode-icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/><path d="M8 7h8M8 11h8M8 15h5"/></svg></span>Summarise
</button>
<button class="di-mode-btn" data-mode="keypoints" onclick="setMode('keypoints',this)">
<span class="di-mode-icon">🔑</span>Key Points
<span class="di-mode-icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><circle cx="8" cy="15" r="4"/><path d="M12 15h8"/><path d="M18 12v6"/></svg></span>Key Points
</button>
<button class="di-mode-btn" data-mode="tables" onclick="setMode('tables',this)">
<span class="di-mode-icon">📊</span>Extract Tables
<span class="di-mode-icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 10h18M9 4v16M15 4v16"/></svg></span>Extract Tables
</button>
<button class="di-mode-btn" data-mode="questions" onclick="setMode('questions',this)">
<span class="di-mode-icon">❓</span>Q&amp;A
<span class="di-mode-icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M9.09 9a3 3 0 1 1 5.82 1c0 2-3 3-3 3"/><path d="M12 17h.01"/><circle cx="12" cy="12" r="9"/></svg></span>Q&amp;A
</button>
<button class="di-mode-btn" data-mode="translate" onclick="setMode('translate',this)">
<span class="di-mode-icon">🌐</span>Translate
<span class="di-mode-icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a15 15 0 0 1 0 18M12 3a15 15 0 0 0 0 18"/></svg></span>Translate
</button>
<div class="di-mode-section">LEGAL &amp; COMPLIANCE</div>
<button class="di-mode-btn" data-mode="clauses" onclick="setMode('clauses',this)">
<span class="di-mode-icon">📋</span>Contract Clauses
<span class="di-mode-icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M9 3h6"/><path d="M10 7h4"/><rect x="5" y="5" width="14" height="16" rx="2"/></svg></span>Contract Clauses
</button>
<button class="di-mode-btn" data-mode="tender" onclick="setMode('tender',this)">
<span class="di-mode-icon">✅</span>Tender Checklist
<span class="di-mode-icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="m20 6-11 11-5-5"/></svg></span>Tender Checklist
</button>
<div class="di-mode-section">INSIGHTS</div>
<button class="di-mode-btn" data-mode="brief" onclick="setMode('brief',this)">
<span class="di-mode-icon">🗒️</span>Executive Brief
<span class="di-mode-icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M8 6h8"/><path d="M8 10h8"/><path d="M8 14h5"/><rect x="4" y="3" width="16" height="18" rx="2"/></svg></span>Executive Brief
</button>
<button class="di-mode-btn" data-mode="compare" onclick="setMode('compare',this)">
<span class="di-mode-icon">🔄</span>Compare Docs
<span class="di-mode-icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M3 7h7v7H3z"/><path d="M14 10h7v7h-7z"/><path d="M14 3h7v4M21 3l-5 5"/><path d="M3 21v-4h7"/><path d="m3 21 5-5"/></svg></span>Compare Docs
</button>
<button class="di-mode-btn" data-mode="custom" onclick="setMode('custom',this)">
<span class="di-mode-icon">✏️</span>Custom Prompt
<span class="di-mode-icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg></span>Custom Prompt
</button>
</div>
@@ -439,13 +467,13 @@
</div>
<button id="process-btn" onclick="processDoc()" disabled>
⚡ Process Document
<span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M13 2 3 14h7l-1 8 10-12h-7z"/></svg></span>Process Document</span>
</button>
</div>
<!-- Progress -->
<div class="di-progress" id="progress-card">
<div class="di-spinner">⚙️</div>
<div class="di-spinner icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/></svg></div>
<div class="di-progress-label" id="progress-label">Extracting text…</div>
<div class="di-progress-sub">This may take 30–120 seconds depending on file size and model</div>
</div>
@@ -453,15 +481,15 @@
<!-- Result -->
<div class="di-result" id="result-card">
<div class="di-result-header">
<span style="font-size:20px" id="result-icon">📝</span>
<span class="icon-svg icon-xl" id="result-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/><path d="M8 7h8M8 11h8M8 15h5"/></svg></span>
<div class="di-result-title" id="result-title">Result</div>
<div class="di-result-meta" id="result-meta"></div>
</div>
<div class="di-result-body" id="result-body"></div>
<div class="di-result-actions">
<button class="di-export-btn" onclick="copyResult()">📋 Copy</button>
<button class="di-export-btn" onclick="downloadMd()">⬇️ Download .md</button>
<button class="di-export-btn" onclick="printPdf()">🖨️ Print / PDF</button>
<button class="di-export-btn" onclick="copyResult()"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="9" y="9" width="11" height="11" rx="2"/><rect x="4" y="4" width="11" height="11" rx="2"/></svg></span>Copy</span></button>
<button class="di-export-btn" onclick="downloadMd()"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 3v12"/><path d="m7 10 5 5 5-5"/><path d="M5 21h14"/></svg></span>Download .md</span></button>
<button class="di-export-btn" onclick="printPdf()"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M6 9V2h12v7"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><path d="M6 14h12v8H6z"/></svg></span>Print / PDF</span></button>
<button class="btn btn-ghost" style="margin-left:auto" onclick="newJob()">+ New Document</button>
</div>
</div>
@@ -489,11 +517,39 @@ let currentMode = 'summarise';
let currentJobId = null;
let pollTimer = null;
let currentResultText = '';
const DOC_ICONS = {
file: '<svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg>',
filePdf: '<svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M8 13h2a2 2 0 1 0 0-4H8v8"/><path d="M13 17h1a2 2 0 0 0 0-4h-1v4"/><path d="M17 9v8"/><path d="M17 13h2"/></svg>',
image: '<svg viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="8.5" cy="10.5" r="1.5"/><path d="m21 15-5-5L5 21"/></svg>',
book: '<svg viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/><path d="M8 7h8M8 11h8M8 15h5"/></svg>',
key: '<svg viewBox="0 0 24 24"><circle cx="8" cy="15" r="4"/><path d="M12 15h8"/><path d="M18 12v6"/></svg>',
table: '<svg viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 10h18M9 4v16M15 4v16"/></svg>',
help: '<svg viewBox="0 0 24 24"><path d="M9.09 9a3 3 0 1 1 5.82 1c0 2-3 3-3 3"/><path d="M12 17h.01"/><circle cx="12" cy="12" r="9"/></svg>',
globe: '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a15 15 0 0 1 0 18M12 3a15 15 0 0 0 0 18"/></svg>',
clipboard: '<svg viewBox="0 0 24 24"><path d="M9 3h6"/><path d="M10 7h4"/><rect x="5" y="5" width="14" height="16" rx="2"/></svg>',
check: '<svg viewBox="0 0 24 24"><path d="m20 6-11 11-5-5"/></svg>',
brief: '<svg viewBox="0 0 24 24"><path d="M8 6h8"/><path d="M8 10h8"/><path d="M8 14h5"/><rect x="4" y="3" width="16" height="18" rx="2"/></svg>',
compare: '<svg viewBox="0 0 24 24"><path d="M3 7h7v7H3z"/><path d="M14 10h7v7h-7z"/><path d="M14 3h7v4M21 3l-5 5"/><path d="M3 21v-4h7"/><path d="m3 21 5-5"/></svg>',
edit: '<svg viewBox="0 0 24 24"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>',
warning: '<svg viewBox="0 0 24 24"><path d="M12 9v4"/><path d="M12 17h.01"/><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/></svg>',
error: '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="m15 9-6 6M9 9l6 6"/></svg>',
trash: '<svg viewBox="0 0 24 24"><path d="M3 6h18"/><path d="M8 6V4h8v2"/><path d="M19 6l-1 14H6L5 6"/></svg>'
};
function iconSvg(name, sizeClass = 'icon-md') {
return `<span class="icon-svg ${sizeClass}" aria-hidden="true">${DOC_ICONS[name] || DOC_ICONS.file}</span>`;
}
const MODE_ICONS = {
summarise:'📝', keypoints:'🔑', tables:'📊', questions:'❓',
translate:'🌐', custom:'✏️', clauses:'📋', tender:'✅',
brief:'🗒️', compare:'🔄'
summarise:iconSvg('book', 'icon-xl'),
keypoints:iconSvg('key', 'icon-xl'),
tables:iconSvg('table', 'icon-xl'),
questions:iconSvg('help', 'icon-xl'),
translate:iconSvg('globe', 'icon-xl'),
custom:iconSvg('edit', 'icon-xl'),
clauses:iconSvg('clipboard', 'icon-xl'),
tender:iconSvg('check', 'icon-xl'),
brief:iconSvg('brief', 'icon-xl'),
compare:iconSvg('compare', 'icon-xl')
};
const MODE_LABELS = {
summarise:'Summary', keypoints:'Key Points', tables:'Extracted Tables',
@@ -529,9 +585,9 @@ fileInputB.addEventListener('change', () => { if (fileInputB.files[0]) selectFil
function selectFile(file) {
selectedFile = file;
const ext = file.name.split('.').pop().toLowerCase();
const icons = { pdf:'📕', docx:'📘', doc:'📘', txt:'📄', md:'📄',
png:'🖼️', jpg:'🖼️', jpeg:'🖼️', webp:'🖼️', bmp:'🖼️' };
document.getElementById('file-icon').textContent = icons[ext] || '📄';
const icons = { pdf:iconSvg('filePdf', 'icon-xl'), docx:iconSvg('file', 'icon-xl'), doc:iconSvg('file', 'icon-xl'), txt:iconSvg('file', 'icon-xl'), md:iconSvg('file', 'icon-xl'),
png:iconSvg('image', 'icon-xl'), jpg:iconSvg('image', 'icon-xl'), jpeg:iconSvg('image', 'icon-xl'), webp:iconSvg('image', 'icon-xl'), bmp:iconSvg('image', 'icon-xl') };
document.getElementById('file-icon').innerHTML = icons[ext] || iconSvg('file', 'icon-xl');
document.getElementById('file-name').textContent = file.name;
document.getElementById('file-size').textContent = fmtSize(file.size);
document.getElementById('file-bar').classList.add('show');
@@ -542,9 +598,9 @@ function selectFile(file) {
function selectFileB(file) {
selectedFileB = file;
const ext = file.name.split('.').pop().toLowerCase();
const icons = { pdf:'📕', docx:'📘', doc:'📘', txt:'📄', md:'📄',
png:'🖼️', jpg:'🖼️', jpeg:'🖼️', webp:'🖼️', bmp:'🖼️' };
document.getElementById('file-icon-b').textContent = icons[ext] || '📄';
const icons = { pdf:iconSvg('filePdf', 'icon-xl'), docx:iconSvg('file', 'icon-xl'), doc:iconSvg('file', 'icon-xl'), txt:iconSvg('file', 'icon-xl'), md:iconSvg('file', 'icon-xl'),
png:iconSvg('image', 'icon-xl'), jpg:iconSvg('image', 'icon-xl'), jpeg:iconSvg('image', 'icon-xl'), webp:iconSvg('image', 'icon-xl'), bmp:iconSvg('image', 'icon-xl') };
document.getElementById('file-icon-b').innerHTML = icons[ext] || iconSvg('file', 'icon-xl');
document.getElementById('file-name-b').textContent = file.name;
document.getElementById('file-size-b').textContent = fmtSize(file.size);
document.getElementById('file-bar-b').classList.add('show');
@@ -673,7 +729,7 @@ function pollJob(id) {
// ── Result rendering ─────────────────────────────────────────────────────────
function showStructuredResult(mode, data, filename) {
document.getElementById('result-icon').textContent = MODE_ICONS[mode] || '📝';
document.getElementById('result-icon').innerHTML = MODE_ICONS[mode] || iconSvg('book', 'icon-xl');
document.getElementById('result-title').textContent = `${MODE_LABELS[mode] || 'Result'} — ${filename}`;
document.getElementById('result-meta').textContent = new Date().toLocaleTimeString();
const body = document.getElementById('result-body');
@@ -709,9 +765,9 @@ function renderClauses(data) {
const counts = {high:0, medium:0, low:0};
data.clauses.forEach(c => counts[c.risk] = (counts[c.risk]||0)+1);
let html = `<div class="di-clause-summary">
<span class="di-clause-stat red">⚠️ ${counts.high||0} High Risk</span>
<span class="di-clause-stat amber">⚡ ${counts.medium||0} Medium Risk</span>
<span class="di-clause-stat green">✓ ${counts.low||0} Low Risk</span>
<span class="di-clause-stat red">${iconSvg('warning', 'icon-sm')} ${counts.high||0} High Risk</span>
<span class="di-clause-stat amber">${iconSvg('compare', 'icon-sm')} ${counts.medium||0} Medium Risk</span>
<span class="di-clause-stat green">${iconSvg('check', 'icon-sm')} ${counts.low||0} Low Risk</span>
</div><div class="di-clauses">`;
data.clauses.forEach((c, i) => {
html += `<div class="di-clause risk-${c.risk}" id="clause-${i}">
@@ -719,7 +775,7 @@ function renderClauses(data) {
<span class="di-clause-type">${c.type}</span>
<span class="di-clause-risk">${c.risk} risk</span>
${c.page ? `<span class="di-clause-pg">p.${c.page}</span>` : ''}
<span class="di-clause-chevron">▶</span>
<span class="di-clause-chevron" aria-hidden="true"><span class="icon-svg icon-sm"><svg viewBox="0 0 24 24"><path d="m9 6 6 6-6 6"/></svg></span></span>
</div>
<div class="di-clause-body">
<div>${c.analysis}</div>
@@ -739,17 +795,17 @@ function renderTenderChecklist(data) {
const total = data.items.length;
const pct = Math.round(pass/total*100);
let html = `<div class="di-checklist-summary">
<span class="di-clause-stat green">✓ ${pass} Met</span>
<span class="di-clause-stat red">✗ ${data.items.filter(i=>i.status==='fail').length} Not Met</span>
<span class="di-clause-stat green">${iconSvg('check', 'icon-sm')} ${pass} Met</span>
<span class="di-clause-stat red">${iconSvg('error', 'icon-sm')} ${data.items.filter(i=>i.status==='fail').length} Not Met</span>
<span class="di-clause-stat amber">? ${data.items.filter(i=>i.status==='missing').length} Missing</span>
</div>
<div class="di-check-prog"><div class="di-check-prog-bar" style="width:${pct}%"></div></div>
<div class="di-checklist">`;
const statusIcon = {pass:'✅', fail:'❌', missing:'⚠️'};
const statusIcon = {pass:iconSvg('check', 'icon-sm'), fail:iconSvg('error', 'icon-sm'), missing:iconSvg('warning', 'icon-sm')};
const statusLabel = {pass:'Met', fail:'Not Met', missing:'Missing'};
data.items.forEach(item => {
html += `<div class="di-check-item ${item.status}">
<span class="di-check-icon">${statusIcon[item.status]||'❓'}</span>
<span class="di-check-icon">${statusIcon[item.status]||iconSvg('help', 'icon-sm')}</span>
<div class="di-check-body">
<div class="di-check-title">${item.requirement}</div>
${item.note ? `<div class="di-check-note">${item.note}</div>` : ''}
@@ -807,11 +863,11 @@ function renderBrief(data) {
function renderCompare(data) {
let html = `<div class="di-compare-grid">
<div class="di-compare-col">
<div class="di-compare-col-header">📄 ${data.file_a || 'Document A'}</div>
<div class="di-compare-col-header">${iconSvg('file', 'icon-sm')} ${data.file_a || 'Document A'}</div>
<div class="di-compare-col-body">${escHtml(data.summary_a || '')}</div>
</div>
<div class="di-compare-col">
<div class="di-compare-col-header">📄 ${data.file_b || 'Document B'}</div>
<div class="di-compare-col-header">${iconSvg('file', 'icon-sm')} ${data.file_b || 'Document B'}</div>
<div class="di-compare-col-body">${escHtml(data.summary_b || '')}</div>
</div>
</div>
@@ -838,7 +894,7 @@ function exportClausesText(data) {
).join('\n\n');
}
function exportTenderText(data) {
const icons = {pass:'✅', fail:'❌', missing:'⚠️'};
const icons = {pass:'[OK]', fail:'[FAIL]', missing:'[WARN]'};
return data.items.map(i => `${icons[i.status]} ${i.requirement}${i.note ? '\n ' + i.note : ''}`).join('\n');
}
function exportBriefText(data) {
@@ -858,10 +914,11 @@ function exportCompareText(data) {
function copyResult() {
navigator.clipboard.writeText(currentResultText).then(() => {
const btn = event.target;
const orig = btn.textContent;
btn.textContent = '✓ Copied!';
setTimeout(() => btn.textContent = orig, 1500);
const btn = event.target.closest('button');
if (!btn) return;
const orig = btn.innerHTML;
btn.innerHTML = `<span class="icon-label">${iconSvg('check', 'icon-sm')}<span>Copied!</span></span>`;
setTimeout(() => btn.innerHTML = orig, 1500);
});
}
@@ -987,7 +1044,7 @@ function renderLocalHistory() {
let html = `<div class="di-hist-table-wrap"><table class="di-hist-table">
<tr><th>File</th><th>Mode</th><th>Status</th><th>When</th></tr>`;
hist.forEach(j => {
const icon = MODE_ICONS[j.mode] || '📄';
const icon = MODE_ICONS[j.mode] || iconSvg('file', 'icon-xl');
const label = MODE_LABELS[j.mode] || j.mode;
const when = j.created_at ? new Date(j.created_at).toLocaleString() : '—';
html += `<tr>
@@ -1017,7 +1074,7 @@ function renderApiHistory(jobs) {
jobs.forEach(j => {
const statusClass = j.status === 'done' ? 'done' : j.status === 'error' ? 'error'
: j.status === 'processing' ? 'processing' : 'pending';
const icon = MODE_ICONS[j.mode] || '📄';
const icon = MODE_ICONS[j.mode] || iconSvg('file', 'icon-xl');
const label = MODE_LABELS[j.mode] || j.mode;
const when = j.created_at ? new Date(j.created_at).toLocaleString() : '—';
html += `<tr>
@@ -1027,7 +1084,7 @@ function renderApiHistory(jobs) {
<td><span class="di-status-badge ${statusClass}">${j.status}</span></td>
<td style="color:var(--lt);font-size:12px">${when}</td>
<td><button class="btn btn-ghost" style="padding:4px 10px;font-size:12px"
onclick="event.stopPropagation();deleteJob(${j.id})">✕</button></td>
onclick="event.stopPropagation();deleteJob(${j.id})">${iconSvg('trash', 'icon-sm')}</button></td>
</tr>`;
});
html += '</table></div>';
@@ -1066,6 +1123,6 @@ loadHistory();
</script>
<script src="auth.js"></script>
<script src="branding.js"></script>
<script src="branding.js?v=3"></script>
</body>
</html>