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

+64 -52
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Meeting Assistant — Nexus One AI</title>
<link rel="stylesheet" href="style.css?v=4">
<link rel="stylesheet" href="style.css?v=12">
<style>
.ma-wrap { display:grid; grid-template-columns:320px 1fr; min-height:calc(100vh - 116px); }
@media(max-width:900px){ .ma-wrap { grid-template-columns:1fr; } }
@@ -17,14 +17,17 @@
/* ── Upload zone ── */
.ma-drop-zone { border:2px dashed var(--bdr); border-radius:12px; padding:28px 16px; text-align:center; cursor:pointer; transition:.15s; background:var(--bg); }
.ma-drop-zone:hover, .ma-drop-zone.drag-over { border-color:var(--purple); background:rgba(124,58,237,.04); }
.ma-drop-icon { font-size:32px; margin-bottom:8px; }
.ma-drop-icon { width:32px; height:32px; margin:0 auto 8px; color:var(--purple); }
.ma-drop-icon svg { width:100%; height:100%; }
.ma-drop-label { font-size:13px; font-weight:600; color:var(--ink); margin-bottom:4px; }
.ma-drop-sub { font-size:11px; color:var(--lt); }
.ma-drop-input { display:none; }
.ma-file-chip { display:flex; align-items:center; gap:10px; padding:10px 14px; background:rgba(124,58,237,.06); border:1px solid rgba(124,58,237,.18); border-radius:10px; margin-top:10px; }
.ma-file-icon { font-size:20px; }
.ma-file-icon { width:20px; height:20px; color:var(--purple); }
.ma-file-icon svg { width:100%; height:100%; }
.ma-file-name { font-size:12px; font-weight:600; color:var(--ink); flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.ma-file-remove{ background:none; border:none; color:var(--lt); cursor:pointer; font-size:16px; padding:0; }
.ma-file-remove{ background:none; border:none; color:var(--lt); cursor:pointer; width:16px; height:16px; padding:0; }
.ma-file-remove svg { width:100%; height:100%; }
.ma-file-remove:hover{ color:#DC2626; }
/* ── Divider ── */
@@ -135,14 +138,16 @@
/* ── Empty state ── */
.ma-empty { text-align:center; padding:80px 20px; color:var(--lt); }
.ma-empty-icon { font-size:56px; margin-bottom:14px; }
.ma-empty-icon { width:56px; height:56px; margin:0 auto 14px; color:var(--purple); }
.ma-empty-icon svg { width:100%; height:100%; }
.ma-empty-title { font-size:18px; font-weight:700; color:var(--ink); margin-bottom:8px; }
.ma-empty-sub { font-size:13px; max-width:380px; margin:0 auto; line-height:1.6; }
/* ── History ── */
.ma-hist-item { display:flex; align-items:center; gap:12px; padding:10px 12px; border-radius:8px; cursor:pointer; transition:.1s; border:1px solid transparent; margin-bottom:4px; }
.ma-hist-item:hover { background:rgba(124,58,237,.04); border-color:var(--bdr); }
.ma-hist-icon { font-size:20px; flex-shrink:0; }
.ma-hist-icon { width:20px; height:20px; flex-shrink:0; color:var(--purple); }
.ma-hist-icon svg { width:100%; height:100%; }
.ma-hist-name { font-size:12px; font-weight:600; color:var(--ink); }
.ma-hist-date { font-size:11px; color:var(--lt); margin-top:2px; }
</style>
@@ -181,6 +186,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>
@@ -220,7 +226,9 @@
</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-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>
@@ -232,13 +240,13 @@
<!-- Upload section -->
<div class="ma-left-header">
<div class="ma-left-title">🎙️ Audio / Transcript Input</div>
<div class="ma-left-title"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="9" y="3" width="6" height="11" rx="3"/><path d="M5 10a7 7 0 0 0 14 0"/><path d="M12 17v4"/><path d="M8 21h8"/></svg></span><span>Audio / Transcript Input</span></span></div>
<!-- Drop zone -->
<div class="ma-drop-zone" id="drop-zone"
onclick="document.getElementById('audio-input').click()"
ondragover="onDragOver(event)" ondragleave="onDragLeave(event)" ondrop="onDrop(event)">
<div class="ma-drop-icon">🎧</div>
<div class="ma-drop-icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M4 12a8 8 0 0 1 16 0"/><rect x="3" y="12" width="4" height="7" rx="2"/><rect x="17" y="12" width="4" height="7" rx="2"/></svg></div>
<div class="ma-drop-label">Drop audio file here</div>
<div class="ma-drop-sub">MP3, M4A, WAV, OGG, WEBM · up to 500MB</div>
</div>
@@ -280,7 +288,7 @@
<div class="ma-att-chips" id="att-chips"></div>
<div class="ma-att-input">
<input type="text" id="att-input" placeholder="Name or email" onkeydown="if(event.key==='Enter')addAttendee()">
<button class="ma-att-add" onclick="addAttendee()">+</button>
<button class="ma-att-add" onclick="addAttendee()"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg></span></button>
</div>
</div>
</div>
@@ -299,9 +307,7 @@
<!-- Run -->
<div class="ma-run-wrap">
<button class="ma-run-btn" id="run-btn" onclick="runMeeting()">
✨ Process Meeting
</button>
<button class="ma-run-btn" id="run-btn" onclick="runMeeting()"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="m8 5 11 7-11 7z"/></svg></span><span>Process Meeting</span></span></button>
</div>
<!-- History (past meetings) -->
@@ -318,17 +324,17 @@
<!-- Progress -->
<div class="ma-progress" id="progress-card">
<div class="ma-progress-title">⚙️ Processing Meeting…</div>
<div class="ma-progress-title"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33h.01A1.65 1.65 0 0 0 10.5 3.1V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51h.01a1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82v.01a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></span><span>Processing Meeting...</span></span></div>
<div class="ma-step-list" id="progress-steps"></div>
</div>
<!-- Empty state -->
<div class="ma-empty" id="empty-state">
<div class="ma-empty-icon">🎙️</div>
<div class="ma-empty-icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="9" y="3" width="6" height="11" rx="3"/><path d="M5 10a7 7 0 0 0 14 0"/><path d="M12 17v4"/><path d="M8 21h8"/></svg></div>
<div class="ma-empty-title">Meeting Assistant</div>
<div class="ma-empty-sub">Upload an audio recording or paste a transcript. The AI will extract a summary, decisions, action items, and generate formal minutes — ready to share.</div>
<div style="margin-top:24px;display:flex;gap:12px;justify-content:center;flex-wrap:wrap">
<button class="btn-primary" onclick="loadDemo()">▶ Try Demo Transcript</button>
<button class="btn-primary" onclick="loadDemo()"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="m8 5 11 7-11 7z"/></svg></span><span>Try Demo Transcript</span></span></button>
</div>
</div>
@@ -344,33 +350,33 @@
</div>
<div class="ma-export-bar" style="margin:0;flex-shrink:0">
<span class="ma-export-label">Export</span>
<button class="ma-export-btn" onclick="exportPDF()">📄 PDF Minutes</button>
<button class="ma-export-btn" onclick="exportMarkdown()">📝 Markdown</button>
<button class="ma-export-btn" onclick="copyToClipboard()">📋 Copy</button>
<button class="ma-export-btn" onclick="exportPDF()"><span class="icon-label"><span class="icon-svg icon-sm" 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>PDF Minutes</span></span></button>
<button class="ma-export-btn" onclick="exportMarkdown()"><span class="icon-label"><span class="icon-svg icon-sm" 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><span>Markdown</span></span></button>
<button class="ma-export-btn" onclick="copyToClipboard()"><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"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg></span><span>Copy</span></span></button>
</div>
</div>
<!-- Tabs -->
<div class="ma-tabs">
<button class="ma-tab active" onclick="showTab('summary')">📋 Summary</button>
<button class="ma-tab" onclick="showTab('actions')">✅ Action Items</button>
<button class="ma-tab" onclick="showTab('decisions')">🏛️ Decisions</button>
<button class="ma-tab" onclick="showTab('transcript')">📜 Transcript</button>
<button class="ma-tab" onclick="showTab('minutes')">📄 Full Minutes</button>
<button class="ma-tab active" onclick="showTab('summary')">Summary</button>
<button class="ma-tab" onclick="showTab('actions')">Action Items</button>
<button class="ma-tab" onclick="showTab('decisions')">Decisions</button>
<button class="ma-tab" onclick="showTab('transcript')">Transcript</button>
<button class="ma-tab" onclick="showTab('minutes')">Full Minutes</button>
</div>
<!-- Summary tab -->
<div class="ma-tab-panel active" id="tab-summary">
<div class="ma-out-card">
<div class="ma-out-hdr"><span>📋</span><div class="ma-out-title">Executive Summary</div></div>
<div class="ma-out-hdr"><span class="icon-svg icon-sm" 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><div class="ma-out-title">Executive Summary</div></div>
<div class="ma-out-body"><div class="ma-out-text" id="out-summary"></div></div>
</div>
<div class="ma-out-card">
<div class="ma-out-hdr"><span>🏷️</span><div class="ma-out-title">Key Topics</div></div>
<div class="ma-out-hdr"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M20 12 12 20l-8-8 8-8 8 8Z"/><circle cx="12" cy="12" r="2.5"/></svg></span><div class="ma-out-title">Key Topics</div></div>
<div class="ma-out-body"><div class="ma-topics" id="out-topics"></div></div>
</div>
<div class="ma-out-card" id="sentiment-card" style="display:none">
<div class="ma-out-hdr"><span>💬</span><div class="ma-out-title">Sentiment Overview</div></div>
<div class="ma-out-hdr"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M21 12a8.5 8.5 0 0 1-8.5 8.5H7l-4 2 1.5-4.5A8.5 8.5 0 1 1 21 12Z"/></svg></span><div class="ma-out-title">Sentiment Overview</div></div>
<div class="ma-out-body" id="out-sentiment"></div>
</div>
</div>
@@ -379,7 +385,7 @@
<div class="ma-tab-panel" id="tab-actions">
<div class="ma-out-card">
<div class="ma-out-hdr">
<span>✅</span>
<span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="m5 12 5 5L20 7"/></svg></span>
<div class="ma-out-title">Action Items</div>
<span style="font-size:12px;color:var(--lt)" id="action-count"></span>
</div>
@@ -400,7 +406,7 @@
<!-- Decisions tab -->
<div class="ma-tab-panel" id="tab-decisions">
<div class="ma-out-card">
<div class="ma-out-hdr"><span>🏛️</span><div class="ma-out-title">Decisions Made</div></div>
<div class="ma-out-hdr"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M3 10h18"/><path d="M5 10v8M10 10v8M14 10v8M19 10v8"/><path d="M2 18h20"/><path d="m12 3 9 5H3l9-5Z"/></svg></span><div class="ma-out-title">Decisions Made</div></div>
<div class="ma-out-body"><div id="out-decisions"></div></div>
</div>
</div>
@@ -409,7 +415,7 @@
<div class="ma-tab-panel" id="tab-transcript">
<div class="ma-out-card">
<div class="ma-out-hdr">
<span>📜</span>
<span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M8 4h8a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H8a3 3 0 0 1 0-6h7"/><path d="M8 14a3 3 0 0 0 0 6"/></svg></span>
<div class="ma-out-title">Full Transcript</div>
<span style="font-size:12px;color:var(--lt)" id="transcript-words"></span>
</div>
@@ -422,7 +428,7 @@
<!-- Full Minutes tab -->
<div class="ma-tab-panel" id="tab-minutes">
<div class="ma-out-card">
<div class="ma-out-hdr"><span>📄</span><div class="ma-out-title">Formal Meeting Minutes</div></div>
<div class="ma-out-hdr"><span class="icon-svg icon-sm" 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"/><path d="M8 13h8M8 17h6"/></svg></span><div class="ma-out-title">Formal Meeting Minutes</div></div>
<div class="ma-out-body">
<div class="ma-out-text" id="out-minutes" style="white-space:pre-wrap;font-family:Georgia,serif;line-height:2"></div>
</div>
@@ -437,6 +443,20 @@
const _API = '/api';
let attendees = [];
let currentResult = null;
function maIconSvg(name, sizeClass = 'icon-sm') {
const icons = {
music:'<svg viewBox="0 0 24 24"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>',
close:'<svg viewBox="0 0 24 24"><path d="M6 6l12 12M18 6 6 18"/></svg>',
user:'<svg viewBox="0 0 24 24"><path d="M20 21a8 8 0 0 0-16 0"/><circle cx="12" cy="8" r="4"/></svg>',
positive:'<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M8.5 14a4 4 0 0 0 7 0"/><path d="M9 10h.01M15 10h.01"/></svg>',
neutral:'<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M9 15h6"/><path d="M9 10h.01M15 10h.01"/></svg>',
concern:'<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M8.5 15.5a4 4 0 0 1 7 0"/><path d="M9 10h.01M15 10h.01"/></svg>',
negative:'<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M8.5 16a4 4 0 0 1 7 0"/><path d="M9 10h.01M15 10h.01"/></svg>',
mixed:'<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M8 15c1-.8 2-.8 3 0s2 .8 3 0 2-.8 3 0"/><path d="M9 10h.01M15 10h.01"/></svg>',
message:'<svg viewBox="0 0 24 24"><path d="M21 12a8.5 8.5 0 0 1-8.5 8.5H7l-4 2 1.5-4.5A8.5 8.5 0 1 1 21 12Z"/></svg>'
};
return `<span class="icon-svg ${sizeClass}" aria-hidden="true">${icons[name] || icons.music}</span>`;
}
// ── Init ───────────────────────────────────────────────────────────────────────
document.getElementById('meeting-date').value = new Date().toISOString().slice(0,10);
@@ -458,10 +478,10 @@ function setFile(file) {
chip.style.display = 'flex';
chip.innerHTML = `
<div class="ma-file-chip">
<span class="ma-file-icon">🎵</span>
<span class="ma-file-icon">${maIconSvg('music')}</span>
<span class="ma-file-name">${esc(file.name)}</span>
<span style="font-size:11px;color:var(--lt);white-space:nowrap">${(file.size/1024/1024).toFixed(1)} MB</span>
<button class="ma-file-remove" onclick="clearFile()" title="Remove">×</button>
<button class="ma-file-remove" onclick="clearFile()" title="Remove">${maIconSvg('close')}</button>
</div>`;
window._selectedFile = file;
// Auto-fill title from filename
@@ -491,7 +511,7 @@ function renderAttendees() {
document.getElementById('att-chips').innerHTML = attendees.map((a,i) => `
<div class="ma-att-chip">
${esc(a)}
<button onclick="removeAttendee(${i})">×</button>
<button onclick="removeAttendee(${i})" aria-label="Remove attendee">&times;</button>
</div>`).join('');
}
@@ -515,7 +535,7 @@ function renderProgress(currentStep) {
const activeIdx = STEPS.findIndex(s => s.key === currentStep);
document.getElementById('progress-steps').innerHTML = STEPS.map((s,i) => {
const cls = i < activeIdx ? 'done' : i === activeIdx ? 'running' : 'pending';
const dot = i < activeIdx ? '✓' : i === activeIdx ? '⚙' : (i+1);
const dot = i < activeIdx ? 'Done' : i === activeIdx ? '...' : (i+1);
return `<div class="ma-step-row">
<div class="ma-step-dot ${cls}">${dot}</div>
<div class="ma-step-label ${cls}">${s.label}${i === activeIdx ? '…' : ''}</div>
@@ -527,11 +547,11 @@ function renderProgress(currentStep) {
async function runMeeting() {
const hasPaste = document.getElementById('paste-transcript').value.trim().length > 10;
const hasFile = !!window._selectedFile;
if (!hasPaste && !hasFile) { alert('Upload an audio file or paste a transcript first.'); return; }
if (!hasPaste && !hasFile) { showToast('Upload an audio file or paste a transcript first.', 'warn'); return; }
const btn = document.getElementById('run-btn');
btn.disabled = true;
btn.textContent = '⚙ Processing…';
btn.innerHTML = `<span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33h.01A1.65 1.65 0 0 0 10.5 3.1V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51h.01a1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82v.01a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></span><span>Processing...</span></span>`;
const opts = {
transcript: document.getElementById('opt-transcript').checked,
@@ -577,7 +597,7 @@ async function runMeeting() {
}
btn.disabled = false;
btn.textContent = '✨ Process Meeting';
btn.innerHTML = `<span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="m8 5 11 7-11 7z"/></svg></span><span>Process Meeting</span></span>`;
}
async function runTextPipeline(text, meta, opts) {
@@ -643,7 +663,7 @@ function showResult(data, meta) {
<div style="display:flex;gap:16px;flex-wrap:wrap">
${Object.entries(data.sentiment).map(([k,v])=>`
<div style="text-align:center;min-width:70px">
<div style="font-size:22px">${sentimentEmoji(k)}</div>
<div style="font-size:22px">${maIconSvg(sentimentIcon(k), 'icon-lg')}</div>
<div style="font-size:13px;font-weight:700;color:var(--ink)">${Math.round(v*100)}%</div>
<div style="font-size:11px;color:var(--lt);text-transform:capitalize">${k}</div>
</div>`).join('')}
@@ -655,7 +675,7 @@ function showResult(data, meta) {
document.getElementById('action-count').textContent = `${actions.length} item${actions.length!==1?'s':''}`;
document.getElementById('actions-body').innerHTML = actions.map(a => `<tr>
<td><div class="ma-action-item">${esc(a.action)}</div></td>
<td>${a.owner ? `<span class="ma-action-owner">👤 ${esc(a.owner)}</span>` : '<span style="color:var(--lt);font-size:12px">Unassigned</span>'}</td>
<td>${a.owner ? `<span class="ma-action-owner">${maIconSvg('user')} ${esc(a.owner)}</span>` : '<span style="color:var(--lt);font-size:12px">Unassigned</span>'}</td>
<td><div class="ma-action-due">${esc(a.due||'—')}</div></td>
<td><span class="ma-priority ${a.priority||'med'}"></span>${capitalize(a.priority||'medium')}</td>
</tr>`).join('');
@@ -894,7 +914,7 @@ function renderHistory() {
if (!hist.length) { el.innerHTML = '<div style="font-size:12px;color:var(--lt);text-align:center;padding:8px 0">No previous meetings</div>'; return; }
el.innerHTML = hist.map((h,i) => `
<div class="ma-hist-item" onclick="loadHistory(${i})">
<div class="ma-hist-icon">📋</div>
<div class="ma-hist-icon"><span class="icon-svg icon-md" 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></div>
<div>
<div class="ma-hist-name">${esc(h.title||'Meeting')}</div>
<div class="ma-hist-date">${h.date ? new Date(h.date).toLocaleDateString('en-GB',{day:'numeric',month:'short',year:'numeric'}) : 'Unknown date'}</div>
@@ -918,20 +938,12 @@ function formatTranscript(text) {
}).join('\n');
}
function sentimentEmoji(k) {
return {positive:'😊',neutral:'😐',concern:'😟',negative:'😔',mixed:'🤔'}[k]||'💬';
function sentimentIcon(k) {
return {positive:'positive',neutral:'neutral',concern:'concern',negative:'negative',mixed:'mixed'}[k] || 'message';
}
function capitalize(s) { return s ? s[0].toUpperCase()+s.slice(1) : ''; }
function showToast(msg) {
const t = document.createElement('div');
t.textContent = msg;
t.style.cssText = 'position:fixed;bottom:24px;right:24px;background:#F0FDFA;color:#0F766E;border:1px solid #99F6E4;border-radius:10px;padding:11px 18px;font-size:13px;font-weight:600;z-index:9999;box-shadow:0 4px 16px rgba(0,0,0,.1)';
document.body.appendChild(t);
setTimeout(()=>t.remove(),2500);
}
function esc(s) {
return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
@@ -941,6 +953,6 @@ renderHistory();
</script>
<script src="auth.js"></script>
<script src="branding.js"></script>
<script src="branding.js?v=3"></script>
</body>
</html>