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:
1 parent
7540da04a8
commit
b3eec34158
57 files changed
+2832
-1354
No files matched your search
+116
-59
@@ -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 & 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&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&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 & 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>
|
||||
Reference in new issue
Block a user