947 lines
48 KiB
HTML
947 lines
48 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<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">
|
||
<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; } }
|
||
|
||
/* ── Left panel ── */
|
||
.ma-left { border-right:1px solid var(--bdr); background:var(--navy2); display:flex; flex-direction:column; }
|
||
.ma-left-header { padding:16px 18px 12px; border-bottom:1px solid var(--bdr); }
|
||
.ma-left-title { font-size:13px; font-weight:700; color:var(--ink); margin-bottom:10px; }
|
||
|
||
/* ── 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-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-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:hover{ color:#DC2626; }
|
||
|
||
/* ── Divider ── */
|
||
.ma-or { display:flex; align-items:center; gap:10px; margin:14px 0; color:var(--lt); font-size:11px; font-weight:700; letter-spacing:.5px; }
|
||
.ma-or::before, .ma-or::after { content:''; flex:1; height:1px; background:var(--bdr); }
|
||
|
||
/* ── Paste transcript ── */
|
||
.ma-paste { width:100%; box-sizing:border-box; padding:10px 12px; border:1.5px solid var(--bdr); border-radius:8px; font-family:inherit; font-size:12px; color:var(--ink); resize:vertical; min-height:100px; background:var(--navy2); }
|
||
.ma-paste:focus { outline:none; border-color:var(--purple); }
|
||
|
||
/* ── Meeting meta ── */
|
||
.ma-meta-section { padding:14px 18px; border-bottom:1px solid var(--bdr); }
|
||
.ma-meta-title { font-size:11px; font-weight:700; color:var(--lt); text-transform:uppercase; letter-spacing:.5px; margin-bottom:10px; }
|
||
.ma-field { display:flex; flex-direction:column; gap:4px; margin-bottom:10px; }
|
||
.ma-field label { font-size:11px; color:var(--lt); font-weight:600; }
|
||
.ma-field input, .ma-field select { padding:8px 10px; border:1.5px solid var(--bdr); border-radius:8px; font-family:inherit; font-size:12px; color:var(--ink); background:var(--navy2); }
|
||
.ma-field input:focus, .ma-field select:focus { outline:none; border-color:var(--purple); }
|
||
|
||
/* ── Attendees ── */
|
||
.ma-att-chips { display:flex; flex-wrap:wrap; gap:6px; margin-top:6px; }
|
||
.ma-att-chip { display:inline-flex; align-items:center; gap:5px; background:rgba(124,58,237,.08); border:1px solid rgba(124,58,237,.18); border-radius:20px; padding:3px 10px; font-size:11px; color:var(--purple); font-weight:600; }
|
||
.ma-att-chip button { background:none; border:none; color:var(--lt); cursor:pointer; font-size:13px; padding:0; line-height:1; }
|
||
.ma-att-chip button:hover { color:#DC2626; }
|
||
.ma-att-input { display:flex; gap:6px; margin-top:6px; }
|
||
.ma-att-input input { flex:1; padding:6px 10px; border:1.5px solid var(--bdr); border-radius:8px; font-family:inherit; font-size:12px; color:var(--ink); background:var(--navy2); }
|
||
.ma-att-add { padding:6px 12px; background:rgba(124,58,237,.08); border:1px solid rgba(124,58,237,.18); border-radius:8px; font-family:inherit; font-size:12px; font-weight:600; color:var(--purple); cursor:pointer; transition:.15s; }
|
||
.ma-att-add:hover { background:rgba(124,58,237,.14); }
|
||
|
||
/* ── Options ── */
|
||
.ma-opts { padding:14px 18px; border-bottom:1px solid var(--bdr); }
|
||
.ma-opt-row { display:flex; align-items:center; gap:10px; margin-bottom:10px; }
|
||
.ma-opt-row:last-child { margin-bottom:0; }
|
||
.ma-opt-row input[type=checkbox] { accent-color:var(--purple); width:14px; height:14px; flex-shrink:0; }
|
||
.ma-opt-label { font-size:12px; color:var(--ink); font-weight:500; }
|
||
|
||
/* ── Run button ── */
|
||
.ma-run-wrap { padding:14px 18px; margin-top:auto; }
|
||
.ma-run-btn { display:flex; align-items:center; justify-content:center; gap:8px; width:100%; padding:12px; background:linear-gradient(135deg,var(--purple),var(--pink)); color:white; border:none; border-radius:10px; font-family:inherit; font-size:14px; font-weight:700; cursor:pointer; transition:.15s; }
|
||
.ma-run-btn:hover { filter:brightness(1.08); transform:translateY(-1px); box-shadow:0 6px 24px rgba(124,58,237,.35); }
|
||
.ma-run-btn:disabled { opacity:.5; cursor:not-allowed; transform:none; }
|
||
|
||
/* ── Right panel (output) ── */
|
||
.ma-right { padding:28px 32px; overflow-y:auto; background:var(--bg); }
|
||
@media(max-width:900px){ .ma-right { padding:20px 16px; } }
|
||
|
||
/* ── Progress ── */
|
||
.ma-progress { background:var(--navy2); border:1px solid var(--bdr); border-radius:14px; padding:28px; margin-bottom:24px; display:none; }
|
||
.ma-progress.active { display:block; }
|
||
.ma-progress-title { font-size:14px; font-weight:700; color:var(--ink); margin-bottom:16px; }
|
||
.ma-step-list { display:flex; flex-direction:column; gap:10px; }
|
||
.ma-step-row { display:flex; align-items:center; gap:12px; }
|
||
.ma-step-dot { width:24px; height:24px; border-radius:50%; border:2px solid var(--bdr); display:flex; align-items:center; justify-content:center; font-size:11px; font-weight:700; flex-shrink:0; transition:.3s; }
|
||
.ma-step-dot.done { background:var(--purple); border-color:var(--purple); color:white; }
|
||
.ma-step-dot.running { background:rgba(124,58,237,.1); border-color:var(--purple); color:var(--purple); animation:pulse 1.2s infinite; }
|
||
.ma-step-dot.pending { background:var(--bg); color:var(--lt); }
|
||
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:.4} }
|
||
.ma-step-label { font-size:13px; color:var(--ink); font-weight:500; }
|
||
.ma-step-label.running { color:var(--purple); }
|
||
.ma-step-label.pending { color:var(--lt); }
|
||
|
||
/* ── Output tabs ── */
|
||
.ma-tabs { display:flex; gap:4px; border-bottom:2px solid var(--bdr); margin-bottom:20px; }
|
||
.ma-tab { padding:8px 18px; border-radius:8px 8px 0 0; font-size:13px; font-weight:600; color:var(--lt); background:none; border:none; cursor:pointer; transition:.15s; font-family:inherit; border-bottom:2px solid transparent; margin-bottom:-2px; }
|
||
.ma-tab:hover { color:var(--ink); }
|
||
.ma-tab.active { color:var(--purple); border-bottom-color:var(--purple); }
|
||
.ma-tab-panel { display:none; }
|
||
.ma-tab-panel.active { display:block; }
|
||
|
||
/* ── Output cards ── */
|
||
.ma-out-card { background:var(--navy2); border:1px solid var(--bdr); border-radius:14px; margin-bottom:16px; overflow:hidden; }
|
||
.ma-out-hdr { padding:14px 18px; border-bottom:1px solid var(--bdr); display:flex; align-items:center; gap:10px; }
|
||
.ma-out-title{ font-size:13px; font-weight:700; color:var(--ink); flex:1; }
|
||
.ma-out-body { padding:18px; }
|
||
.ma-out-text { font-size:13px; color:var(--ink); line-height:1.75; white-space:pre-wrap; }
|
||
|
||
/* ── Transcript ── */
|
||
.ma-transcript { font-size:12.5px; color:var(--ink); line-height:1.8; white-space:pre-wrap; max-height:480px; overflow-y:auto; background:var(--bg); border-radius:8px; padding:14px; border:1px solid var(--bdr); }
|
||
.ma-transcript .speaker { font-weight:700; color:var(--purple); }
|
||
|
||
/* ── Action items table ── */
|
||
.ma-action-table { width:100%; border-collapse:collapse; }
|
||
.ma-action-table th { font-size:11px; font-weight:700; color:var(--lt); text-transform:uppercase; letter-spacing:.4px; padding:0 0 10px; text-align:left; border-bottom:1px solid var(--bdr); }
|
||
.ma-action-table td { padding:11px 0; border-bottom:1px solid var(--bdr); font-size:13px; vertical-align:top; }
|
||
.ma-action-table tr:last-child td { border-bottom:none; }
|
||
.ma-action-item { font-weight:500; color:var(--ink); }
|
||
.ma-action-owner{ display:inline-flex; align-items:center; gap:5px; background:rgba(124,58,237,.08); border:1px solid rgba(124,58,237,.18); border-radius:20px; padding:2px 10px; font-size:11px; color:var(--purple); font-weight:600; }
|
||
.ma-action-due { font-size:11px; color:var(--lt); }
|
||
.ma-priority { display:inline-block; width:8px; height:8px; border-radius:50%; margin-right:6px; }
|
||
.ma-priority.high{ background:#DC2626; }
|
||
.ma-priority.med { background:#D97706; }
|
||
.ma-priority.low { background:#16A34A; }
|
||
|
||
/* ── Decisions list ── */
|
||
.ma-decision { display:flex; gap:12px; padding:11px 0; border-bottom:1px solid var(--bdr); }
|
||
.ma-decision:last-child { border-bottom:none; }
|
||
.ma-decision-num { width:22px; height:22px; border-radius:50%; background:linear-gradient(135deg,var(--purple),var(--pink)); color:white; font-size:10px; font-weight:800; display:flex; align-items:center; justify-content:center; flex-shrink:0; margin-top:2px; }
|
||
.ma-decision-text { font-size:13px; color:var(--ink); line-height:1.6; }
|
||
|
||
/* ── Key topics ── */
|
||
.ma-topics { display:flex; flex-wrap:wrap; gap:8px; }
|
||
.ma-topic-chip { background:rgba(124,58,237,.07); border:1px solid rgba(124,58,237,.18); border-radius:20px; padding:5px 14px; font-size:12px; font-weight:600; color:var(--purple); }
|
||
|
||
/* ── Export bar ── */
|
||
.ma-export-bar { display:flex; gap:10px; flex-wrap:wrap; padding:16px 18px; background:var(--navy2); border:1px solid var(--bdr); border-radius:14px; margin-bottom:20px; align-items:center; }
|
||
.ma-export-label { font-size:12px; font-weight:700; color:var(--lt); text-transform:uppercase; letter-spacing:.5px; margin-right:4px; }
|
||
.ma-export-btn { display:inline-flex; align-items:center; gap:6px; padding:7px 16px; border-radius:20px; border:1px solid var(--bdr); background:var(--bg); font-family:inherit; font-size:12px; font-weight:600; color:var(--med); cursor:pointer; transition:.15s; }
|
||
.ma-export-btn:hover { border-color:var(--purple); color:var(--purple); background:rgba(124,58,237,.04); }
|
||
|
||
/* ── Empty state ── */
|
||
.ma-empty { text-align:center; padding:80px 20px; color:var(--lt); }
|
||
.ma-empty-icon { font-size:56px; margin-bottom:14px; }
|
||
.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-name { font-size:12px; font-weight:600; color:var(--ink); }
|
||
.ma-hist-date { font-size:11px; color:var(--lt); margin-top:2px; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<header class="topnav">
|
||
<a href="index.html" class="brand">Nexus One <span>AI</span></a>
|
||
<nav>
|
||
<a href="index.html">Home</a>
|
||
<a href="quickstart.html">Quick Start</a>
|
||
<a href="prompts.html">Prompt Library</a>
|
||
<a href="usecases.html">Use Cases</a>
|
||
<span class="nav-sep"></span>
|
||
<div class="nav-dropdown">
|
||
<button class="nav-drop-btn">Help ▾</button>
|
||
<div class="nav-drop-menu">
|
||
<span class="nav-drop-cat">LEARN /</span>
|
||
<a href="quickstart.html">Quick Start</a>
|
||
<a href="models.html">Models</a>
|
||
<span class="nav-drop-cat">SUPPORT /</span>
|
||
<a href="troubleshooting.html">Troubleshoot</a>
|
||
<a href="faq.html">FAQ</a>
|
||
<span class="nav-drop-cat">MORE /</span>
|
||
<a href="glossary.html">Glossary</a>
|
||
<a href="whats-new.html">What's New</a>
|
||
</div>
|
||
</div>
|
||
<div class="nav-dropdown">
|
||
<button class="nav-drop-btn">Admin ▾</button>
|
||
<div class="nav-drop-menu nav-drop-menu-wide">
|
||
<span class="nav-drop-cat">DOCS /</span>
|
||
<a href="security.html">Security & Privacy</a>
|
||
<a href="admin.html">Admin Guide</a>
|
||
<span class="nav-drop-cat">MONITOR /</span>
|
||
<a href="dashboard.html">Dashboard</a>
|
||
<a href="analytics.html">Usage Analytics</a>
|
||
<a href="audit.html">Audit Log</a>
|
||
<a href="feedback.html">Feedback & Ratings</a>
|
||
<span class="nav-drop-cat">MANAGE /</span>
|
||
<a href="users.html">Users</a>
|
||
<a href="teams.html">Teams</a>
|
||
<a href="models-admin.html">Model Manager</a>
|
||
<a href="training.html">Training</a>
|
||
<a href="knowledge.html">Knowledge Base</a>
|
||
<span class="nav-drop-cat">TOOLS /</span>
|
||
<a href="apikeys.html">API Keys</a>
|
||
<a href="benchmark.html">Benchmarking</a>
|
||
<a href="model-compare.html">Model Compare</a>
|
||
<a href="api-playground.html">API Playground</a>
|
||
<a href="guardrails.html">Guardrails</a>
|
||
<a href="rag-quality.html">RAG Quality</a>
|
||
<a href="router.html">Model Router</a>
|
||
<a href="connectors.html">Connectors</a>
|
||
<span class="nav-drop-cat">SYSTEM /</span>
|
||
<a href="console.html">Console</a>
|
||
<a href="settings.html">Settings</a>
|
||
</div>
|
||
</div>
|
||
<div class="nav-dropdown">
|
||
<button class="nav-drop-btn active">AI Tools ▾</button>
|
||
<div class="nav-drop-menu">
|
||
<span class="nav-drop-cat">INTELLIGENCE /</span>
|
||
<a href="documents.html">Document Intelligence</a>
|
||
<a href="chat-multi.html">Multimodal Chat</a>
|
||
<a href="prompt-studio.html">Prompt Studio</a>
|
||
<a href="meeting.html">Meeting Assistant</a>
|
||
<span class="nav-drop-cat">AUTOMATION /</span>
|
||
<a href="agents.html">Agent Builder</a>
|
||
<a href="schedules.html">Scheduled Jobs</a>
|
||
<a href="workflows.html">Workflow Automation</a>
|
||
<span class="nav-drop-cat">QUALITY /</span>
|
||
<a href="evals.html">AI Eval Suite</a>
|
||
<a href="chatrooms.html">Chat Rooms</a>
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
<a href="notifications.html" style="position:relative">🔔</a>
|
||
<span class="badge" data-brand="tier">Basic Tier</span>
|
||
<div id="nav-org-logo" class="nav-org-logo"></div>
|
||
</header>
|
||
|
||
<div class="ma-wrap">
|
||
|
||
<!-- ── Left panel ── -->
|
||
<aside class="ma-left">
|
||
|
||
<!-- Upload section -->
|
||
<div class="ma-left-header">
|
||
<div class="ma-left-title">🎙️ Audio / Transcript Input</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-label">Drop audio file here</div>
|
||
<div class="ma-drop-sub">MP3, M4A, WAV, OGG, WEBM · up to 500MB</div>
|
||
</div>
|
||
<input type="file" id="audio-input" class="ma-drop-input" accept="audio/*,video/mp4,video/webm" onchange="onFileSelect(this)">
|
||
<div id="file-chip" style="display:none"></div>
|
||
|
||
<div class="ma-or">OR</div>
|
||
|
||
<!-- Paste transcript -->
|
||
<label style="font-size:11px;font-weight:700;color:var(--lt);text-transform:uppercase;letter-spacing:.4px;display:block;margin-bottom:5px">Paste Transcript</label>
|
||
<textarea class="ma-paste" id="paste-transcript" placeholder="Paste meeting transcript here — speaker labels like 'John: ...' will be detected automatically…" rows="6"></textarea>
|
||
</div>
|
||
|
||
<!-- Meeting metadata -->
|
||
<div class="ma-meta-section">
|
||
<div class="ma-meta-title">Meeting Details</div>
|
||
<div class="ma-field">
|
||
<label>Meeting Title</label>
|
||
<input type="text" id="meeting-title" placeholder="e.g. Q3 Budget Review">
|
||
</div>
|
||
<div class="ma-field">
|
||
<label>Date</label>
|
||
<input type="date" id="meeting-date">
|
||
</div>
|
||
<div class="ma-field">
|
||
<label>Type</label>
|
||
<select id="meeting-type">
|
||
<option value="general">General Meeting</option>
|
||
<option value="project">Project Review</option>
|
||
<option value="board">Board / Executive</option>
|
||
<option value="standup">Stand-up / Sync</option>
|
||
<option value="interview">Interview</option>
|
||
<option value="client">Client Call</option>
|
||
<option value="training">Training / Workshop</option>
|
||
</select>
|
||
</div>
|
||
<div class="ma-field">
|
||
<label>Attendees</label>
|
||
<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>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Options -->
|
||
<div class="ma-opts">
|
||
<div class="ma-meta-title">Processing Options</div>
|
||
<div class="ma-opt-row"><input type="checkbox" id="opt-transcript" checked><label class="ma-opt-label">Full transcript</label></div>
|
||
<div class="ma-opt-row"><input type="checkbox" id="opt-summary" checked><label class="ma-opt-label">Executive summary</label></div>
|
||
<div class="ma-opt-row"><input type="checkbox" id="opt-decisions" checked><label class="ma-opt-label">Decisions log</label></div>
|
||
<div class="ma-opt-row"><input type="checkbox" id="opt-actions" checked><label class="ma-opt-label">Action items with owners</label></div>
|
||
<div class="ma-opt-row"><input type="checkbox" id="opt-topics" checked><label class="ma-opt-label">Key topics / keywords</label></div>
|
||
<div class="ma-opt-row"><input type="checkbox" id="opt-sentiment"><label class="ma-opt-label">Sentiment analysis</label></div>
|
||
<div class="ma-opt-row"><input type="checkbox" id="opt-minutes" checked><label class="ma-opt-label">Generate formal minutes</label></div>
|
||
</div>
|
||
|
||
<!-- Run -->
|
||
<div class="ma-run-wrap">
|
||
<button class="ma-run-btn" id="run-btn" onclick="runMeeting()">
|
||
✨ Process Meeting
|
||
</button>
|
||
</div>
|
||
|
||
<!-- History (past meetings) -->
|
||
<div style="border-top:1px solid var(--bdr);padding:12px 18px;flex-shrink:0" id="history-section">
|
||
<div class="ma-meta-title" style="margin-bottom:8px">Recent Meetings</div>
|
||
<div id="history-list">
|
||
<div style="font-size:12px;color:var(--lt);text-align:center;padding:8px 0">No previous meetings</div>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- ── Right panel (output) ── -->
|
||
<main class="ma-right">
|
||
|
||
<!-- Progress -->
|
||
<div class="ma-progress" id="progress-card">
|
||
<div class="ma-progress-title">⚙️ Processing Meeting…</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-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>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Output area (hidden until processed) -->
|
||
<div id="output-area" style="display:none">
|
||
|
||
<!-- Meeting header -->
|
||
<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:20px;flex-wrap:wrap">
|
||
<div>
|
||
<div style="font-size:11px;font-weight:700;color:var(--lt);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px">Meeting Minutes</div>
|
||
<div style="font-size:22px;font-weight:800;color:var(--ink)" id="out-title">—</div>
|
||
<div style="font-size:13px;color:var(--lt);margin-top:4px" id="out-meta">—</div>
|
||
</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>
|
||
</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>
|
||
</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-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-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-body" id="out-sentiment"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Actions tab -->
|
||
<div class="ma-tab-panel" id="tab-actions">
|
||
<div class="ma-out-card">
|
||
<div class="ma-out-hdr">
|
||
<span>✅</span>
|
||
<div class="ma-out-title">Action Items</div>
|
||
<span style="font-size:12px;color:var(--lt)" id="action-count"></span>
|
||
</div>
|
||
<div class="ma-out-body">
|
||
<table class="ma-action-table" id="out-actions">
|
||
<thead><tr>
|
||
<th style="width:46%">Action</th>
|
||
<th style="width:22%">Owner</th>
|
||
<th style="width:16%">Due</th>
|
||
<th style="width:16%">Priority</th>
|
||
</tr></thead>
|
||
<tbody id="actions-body"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 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-body"><div id="out-decisions"></div></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Transcript tab -->
|
||
<div class="ma-tab-panel" id="tab-transcript">
|
||
<div class="ma-out-card">
|
||
<div class="ma-out-hdr">
|
||
<span>📜</span>
|
||
<div class="ma-out-title">Full Transcript</div>
|
||
<span style="font-size:12px;color:var(--lt)" id="transcript-words"></span>
|
||
</div>
|
||
<div class="ma-out-body">
|
||
<div class="ma-transcript" id="out-transcript"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 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-body">
|
||
<div class="ma-out-text" id="out-minutes" style="white-space:pre-wrap;font-family:Georgia,serif;line-height:2"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div><!-- /#output-area -->
|
||
</main>
|
||
</div>
|
||
|
||
<script>
|
||
const _API = '/api';
|
||
let attendees = [];
|
||
let currentResult = null;
|
||
|
||
// ── Init ───────────────────────────────────────────────────────────────────────
|
||
document.getElementById('meeting-date').value = new Date().toISOString().slice(0,10);
|
||
|
||
// ── File drag/drop ─────────────────────────────────────────────────────────────
|
||
function onDragOver(e) { e.preventDefault(); document.getElementById('drop-zone').classList.add('drag-over'); }
|
||
function onDragLeave() { document.getElementById('drop-zone').classList.remove('drag-over'); }
|
||
function onDrop(e) {
|
||
e.preventDefault();
|
||
onDragLeave();
|
||
const file = e.dataTransfer.files[0];
|
||
if (file) setFile(file);
|
||
}
|
||
function onFileSelect(input) {
|
||
if (input.files[0]) setFile(input.files[0]);
|
||
}
|
||
function setFile(file) {
|
||
const chip = document.getElementById('file-chip');
|
||
chip.style.display = 'flex';
|
||
chip.innerHTML = `
|
||
<div class="ma-file-chip">
|
||
<span class="ma-file-icon">🎵</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>
|
||
</div>`;
|
||
window._selectedFile = file;
|
||
// Auto-fill title from filename
|
||
if (!document.getElementById('meeting-title').value) {
|
||
document.getElementById('meeting-title').value = file.name.replace(/\.[^.]+$/,'').replace(/[-_]/g,' ');
|
||
}
|
||
}
|
||
function clearFile() {
|
||
window._selectedFile = null;
|
||
document.getElementById('file-chip').style.display = 'none';
|
||
document.getElementById('audio-input').value = '';
|
||
}
|
||
|
||
// ── Attendees ──────────────────────────────────────────────────────────────────
|
||
function addAttendee() {
|
||
const v = document.getElementById('att-input').value.trim();
|
||
if (!v || attendees.includes(v)) return;
|
||
attendees.push(v);
|
||
renderAttendees();
|
||
document.getElementById('att-input').value = '';
|
||
}
|
||
function removeAttendee(i) {
|
||
attendees.splice(i,1);
|
||
renderAttendees();
|
||
}
|
||
function renderAttendees() {
|
||
document.getElementById('att-chips').innerHTML = attendees.map((a,i) => `
|
||
<div class="ma-att-chip">
|
||
${esc(a)}
|
||
<button onclick="removeAttendee(${i})">×</button>
|
||
</div>`).join('');
|
||
}
|
||
|
||
// ── Progress steps ─────────────────────────────────────────────────────────────
|
||
const STEPS = [
|
||
{ key:'upload', label:'Uploading audio file' },
|
||
{ key:'transcribe', label:'Transcribing with Whisper' },
|
||
{ key:'summary', label:'Generating executive summary' },
|
||
{ key:'decisions', label:'Extracting decisions' },
|
||
{ key:'actions', label:'Identifying action items with owners' },
|
||
{ key:'topics', label:'Extracting key topics' },
|
||
{ key:'minutes', label:'Writing formal minutes' },
|
||
];
|
||
|
||
function renderProgress(currentStep) {
|
||
const card = document.getElementById('progress-card');
|
||
card.classList.add('active');
|
||
document.getElementById('empty-state').style.display = 'none';
|
||
document.getElementById('output-area').style.display = 'none';
|
||
|
||
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);
|
||
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>
|
||
</div>`;
|
||
}).join('');
|
||
}
|
||
|
||
// ── Run ────────────────────────────────────────────────────────────────────────
|
||
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; }
|
||
|
||
const btn = document.getElementById('run-btn');
|
||
btn.disabled = true;
|
||
btn.textContent = '⚙ Processing…';
|
||
|
||
const opts = {
|
||
transcript: document.getElementById('opt-transcript').checked,
|
||
summary: document.getElementById('opt-summary').checked,
|
||
decisions: document.getElementById('opt-decisions').checked,
|
||
actions: document.getElementById('opt-actions').checked,
|
||
topics: document.getElementById('opt-topics').checked,
|
||
sentiment: document.getElementById('opt-sentiment').checked,
|
||
minutes: document.getElementById('opt-minutes').checked,
|
||
};
|
||
|
||
const meta = {
|
||
title: document.getElementById('meeting-title').value || 'Untitled Meeting',
|
||
date: document.getElementById('meeting-date').value,
|
||
type: document.getElementById('meeting-type').value,
|
||
attendees: [...attendees],
|
||
};
|
||
|
||
try {
|
||
if (hasFile) {
|
||
// Upload + transcribe via API
|
||
renderProgress('upload');
|
||
const formData = new FormData();
|
||
formData.append('file', window._selectedFile);
|
||
formData.append('meta', JSON.stringify({...meta, opts}));
|
||
|
||
renderProgress('transcribe');
|
||
const res = await fetch(`${_API}/meeting/process`, {
|
||
method:'POST', credentials:'include', body: formData
|
||
});
|
||
if (!res.ok) throw new Error((await res.json()).detail || 'Processing failed');
|
||
const data = await res.json();
|
||
renderProgress('minutes');
|
||
await new Promise(r=>setTimeout(r,400));
|
||
showResult(data, meta);
|
||
} else {
|
||
// Text-only — use LLM pipeline
|
||
await runTextPipeline(document.getElementById('paste-transcript').value, meta, opts);
|
||
}
|
||
} catch(e) {
|
||
// Fall back to demo if API unavailable
|
||
await runDemoPipeline(document.getElementById('paste-transcript').value || DEMO_TRANSCRIPT, meta, opts);
|
||
}
|
||
|
||
btn.disabled = false;
|
||
btn.textContent = '✨ Process Meeting';
|
||
}
|
||
|
||
async function runTextPipeline(text, meta, opts) {
|
||
const steps = [
|
||
{ key:'summary', label:'Generating executive summary' },
|
||
{ key:'decisions', label:'Extracting decisions' },
|
||
{ key:'actions', label:'Identifying action items' },
|
||
{ key:'topics', label:'Extracting key topics' },
|
||
{ key:'minutes', label:'Writing formal minutes' },
|
||
].filter(s => opts[s.key] !== false);
|
||
|
||
for (const s of steps) {
|
||
renderProgress(s.key);
|
||
await new Promise(r => setTimeout(r, 600 + Math.random()*400));
|
||
}
|
||
|
||
// Try real API
|
||
try {
|
||
const res = await fetch(`${_API}/meeting/analyse`, {
|
||
method:'POST', credentials:'include',
|
||
headers:{'Content-Type':'application/json'},
|
||
body: JSON.stringify({ transcript: text, meta, opts })
|
||
});
|
||
if (res.ok) { showResult(await res.json(), meta); return; }
|
||
} catch(_) {}
|
||
|
||
// Fallback: generate locally using mock
|
||
await runDemoPipeline(text, meta, opts);
|
||
}
|
||
|
||
async function runDemoPipeline(text, meta, opts) {
|
||
const result = generateMockResult(text, meta);
|
||
renderProgress('minutes');
|
||
await new Promise(r=>setTimeout(r,500));
|
||
showResult(result, meta);
|
||
saveToHistory(meta, result);
|
||
}
|
||
|
||
// ── Show result ────────────────────────────────────────────────────────────────
|
||
function showResult(data, meta) {
|
||
currentResult = data;
|
||
document.getElementById('progress-card').classList.remove('active');
|
||
document.getElementById('empty-state').style.display = 'none';
|
||
document.getElementById('output-area').style.display = '';
|
||
|
||
document.getElementById('out-title').textContent = meta.title || 'Meeting Minutes';
|
||
document.getElementById('out-meta').textContent = [
|
||
meta.date ? new Date(meta.date).toLocaleDateString('en-GB',{day:'numeric',month:'long',year:'numeric'}) : '',
|
||
meta.attendees?.length ? meta.attendees.join(', ') : '',
|
||
].filter(Boolean).join(' · ');
|
||
|
||
// Summary
|
||
document.getElementById('out-summary').textContent = data.summary || '';
|
||
|
||
// Topics
|
||
document.getElementById('out-topics').innerHTML = (data.topics || []).map(t =>
|
||
`<div class="ma-topic-chip">${esc(t)}</div>`).join('');
|
||
|
||
// Sentiment
|
||
if (data.sentiment) {
|
||
document.getElementById('sentiment-card').style.display = '';
|
||
document.getElementById('out-sentiment').innerHTML = `
|
||
<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: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('')}
|
||
</div>`;
|
||
}
|
||
|
||
// Action items
|
||
const actions = data.action_items || [];
|
||
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><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('');
|
||
|
||
// Decisions
|
||
document.getElementById('out-decisions').innerHTML = (data.decisions || []).map((d,i) =>
|
||
`<div class="ma-decision">
|
||
<div class="ma-decision-num">${i+1}</div>
|
||
<div class="ma-decision-text">${esc(d)}</div>
|
||
</div>`).join('') || '<div style="color:var(--lt);font-size:13px">No explicit decisions recorded</div>';
|
||
|
||
// Transcript
|
||
const tx = data.transcript || document.getElementById('paste-transcript').value || '';
|
||
const wordCount = tx.trim().split(/\s+/).length;
|
||
document.getElementById('transcript-words').textContent = `${wordCount.toLocaleString()} words`;
|
||
document.getElementById('out-transcript').innerHTML = formatTranscript(tx);
|
||
|
||
// Minutes
|
||
document.getElementById('out-minutes').textContent = data.minutes || generateMinutesText(data, meta);
|
||
|
||
showTab('summary');
|
||
}
|
||
|
||
// ── Demo ───────────────────────────────────────────────────────────────────────
|
||
const DEMO_TRANSCRIPT = `Priya: Good morning everyone. Let's get started with the Q3 budget review. Can someone confirm we have quorum?
|
||
|
||
Rajan: Yes, we have Priya, myself, Ananya, and Suresh on the call.
|
||
|
||
Priya: Perfect. So the main agenda today is reviewing the Q3 spend against forecast, deciding on the server upgrade budget, and looking at the training plan for the new AI tools.
|
||
|
||
Ananya: Before we start — just to flag, IT submitted a late request for additional storage capacity. Should we add that to the agenda?
|
||
|
||
Priya: Yes, let's take that after the server discussion. Suresh, can you walk us through Q3 actuals?
|
||
|
||
Suresh: Sure. Overall we came in at 94% of forecast, which is good news. The main variance was in infrastructure — we underspent by about 8% because the server upgrade was deferred. On the people side, we're exactly on track.
|
||
|
||
Rajan: The 8% infrastructure underspend — can that roll over to Q4?
|
||
|
||
Suresh: Yes, finance confirmed that. About ₹12 lakhs can carry over.
|
||
|
||
Priya: Good. Now for the server upgrade — Ananya, what are we recommending?
|
||
|
||
Ananya: We're recommending Option B: three new GPU nodes with 2TB SSD each, which comes to ₹38 lakhs including setup. Option A was cheaper at ₹24 lakhs but only one GPU node, which won't support the new AI workloads we're planning.
|
||
|
||
Rajan: I agree Option B makes more sense given where we're heading. The AI suite alone needs at least two nodes running in parallel for reliability.
|
||
|
||
Priya: Agreed. We'll go with Option B. Ananya, can you get the purchase order raised by end of month?
|
||
|
||
Ananya: Will do. I'll also loop in Suresh for the finance approvals.
|
||
|
||
Suresh: I'll prioritise the approval. Should be done within a week.
|
||
|
||
Priya: Now the storage request from IT — Ananya, what did they ask for?
|
||
|
||
Ananya: An extra 10TB NAS for the document archive. They said current storage hits 85% capacity by December.
|
||
|
||
Priya: That needs to be addressed before December. Let's add 10TB to the Q4 plan. Suresh, can you find budget for that?
|
||
|
||
Suresh: I'll check, but given the Q3 carryover we should have room. I'll confirm by Thursday.
|
||
|
||
Priya: Great. Now, training. Rajan, where are we on the AI tools training for the team?
|
||
|
||
Rajan: We have 28 staff who need onboarding on the new Nexus One AI suite. I'd like to run two workshops in October — one for the core users and one for the admin team. I need budget approval for an external trainer, approximately ₹80,000.
|
||
|
||
Priya: That's reasonable. Approved. Can you share the workshop schedule with everyone by next week?
|
||
|
||
Rajan: Yes, I'll have it out by Friday.
|
||
|
||
Priya: Good. Any other business?
|
||
|
||
Ananya: Just a reminder that the vendor review for the current cloud contracts is due in November. We should start that conversation soon given we're moving more to local AI.
|
||
|
||
Priya: Noted. Let's schedule a dedicated session for that in the first week of November. Rajan, can you set that up?
|
||
|
||
Rajan: Yes, I'll send out a calendar invite this week.
|
||
|
||
Priya: Perfect. Thank you all. Let me recap the key decisions — we're going with Option B for the server upgrade at ₹38 lakhs, adding 10TB NAS storage to Q4, approving ₹80,000 for AI training workshops, and scheduling a vendor review for November. Everyone aligned?
|
||
|
||
All: Yes.
|
||
|
||
Priya: Great. Thanks everyone. We'll reconvene in four weeks.`;
|
||
|
||
function loadDemo() {
|
||
document.getElementById('paste-transcript').value = DEMO_TRANSCRIPT;
|
||
document.getElementById('meeting-title').value = 'Q3 Budget Review';
|
||
attendees = ['Priya', 'Rajan', 'Ananya', 'Suresh'];
|
||
renderAttendees();
|
||
document.getElementById('meeting-type').value = 'project';
|
||
}
|
||
|
||
function generateMockResult(text, meta) {
|
||
const isDemo = text.includes('Q3 Budget Review') || text.includes('Priya');
|
||
return {
|
||
transcript: text,
|
||
summary: isDemo
|
||
? 'The Q3 budget review confirmed overall spend at 94% of forecast, with an ₹12 lakh infrastructure underspend carrying over to Q4. The team approved the Option B server upgrade (₹38 lakhs, 3 GPU nodes), added 10TB NAS storage to the Q4 plan, approved ₹80,000 for AI tools training workshops, and scheduled a cloud vendor review for November.'
|
||
: 'Meeting covered key agenda items. Participants discussed current status, upcoming priorities, and assigned follow-up actions. Several decisions were made regarding resource allocation and timelines.',
|
||
topics: isDemo
|
||
? ['Q3 Budget', 'Server Upgrade', 'GPU Infrastructure', 'AI Training', 'Storage Capacity', 'Vendor Review', 'Q4 Planning']
|
||
: ['Budget Review', 'Project Status', 'Team Updates', 'Next Steps'],
|
||
decisions: isDemo
|
||
? [
|
||
'Approved Option B server upgrade — 3 GPU nodes + 2TB SSD each — at ₹38 lakhs including setup.',
|
||
'Added 10TB NAS storage to Q4 budget plan to address IT capacity requirement.',
|
||
'Approved ₹80,000 budget for two AI tools training workshops in October.',
|
||
'Scheduled cloud vendor contract review for first week of November.',
|
||
'Confirmed ₹12 lakh Q3 infrastructure carryover to Q4.',
|
||
]
|
||
: ['Decision on primary agenda item approved by all attendees.', 'Follow-up review scheduled for next month.'],
|
||
action_items: isDemo
|
||
? [
|
||
{ action:'Raise purchase order for Option B server upgrade (₹38L)', owner:'Ananya', due:'End of October', priority:'high' },
|
||
{ action:'Process finance approval for server upgrade PO', owner:'Suresh', due:'Within 1 week', priority:'high' },
|
||
{ action:'Confirm Q4 budget allocation for 10TB NAS storage', owner:'Suresh', due:'Thursday', priority:'med' },
|
||
{ action:'Share AI training workshop schedule with all staff', owner:'Rajan', due:'Friday', priority:'med' },
|
||
{ action:'Send calendar invite for November cloud vendor review', owner:'Rajan', due:'This week', priority:'med' },
|
||
{ action:'Initiate cloud vendor contract review discussions', owner:'Ananya', due:'November Week 1', priority:'low' },
|
||
]
|
||
: [
|
||
{ action:'Follow up on key discussion point', owner:'Team Lead', due:'Next week', priority:'high' },
|
||
{ action:'Share updated document with team', owner:'Secretary', due:'Tomorrow', priority:'med' },
|
||
],
|
||
sentiment: { positive: 0.68, neutral: 0.26, concern: 0.06 },
|
||
minutes: null, // generated below
|
||
};
|
||
}
|
||
|
||
function generateMinutesText(data, meta) {
|
||
const date = meta.date
|
||
? new Date(meta.date).toLocaleDateString('en-GB',{weekday:'long',day:'numeric',month:'long',year:'numeric'})
|
||
: new Date().toLocaleDateString('en-GB',{weekday:'long',day:'numeric',month:'long',year:'numeric'});
|
||
|
||
const atts = meta.attendees?.length ? meta.attendees.join(', ') : 'As per attendance register';
|
||
|
||
const decisionsText = (data.decisions||[]).map((d,i)=>` ${i+1}. ${d}`).join('\n') || ' (None recorded)';
|
||
const actionsText = (data.action_items||[]).map(a=>` • ${a.action} — Owner: ${a.owner||'TBD'}, Due: ${a.due||'TBD'}`).join('\n') || ' (None recorded)';
|
||
|
||
return `MINUTES OF MEETING
|
||
${'═'.repeat(60)}
|
||
|
||
Meeting Title : ${meta.title || 'Meeting'}
|
||
Date : ${date}
|
||
Meeting Type : ${capitalize(meta.type||'General')}
|
||
Attendees : ${atts}
|
||
Recorded by : Nexus One AI Meeting Assistant
|
||
${'─'.repeat(60)}
|
||
|
||
1. EXECUTIVE SUMMARY
|
||
|
||
${data.summary || ''}
|
||
|
||
${'─'.repeat(60)}
|
||
|
||
2. KEY TOPICS DISCUSSED
|
||
|
||
${(data.topics||[]).map(t=>` • ${t}`).join('\n') || ' (None recorded)'}
|
||
|
||
${'─'.repeat(60)}
|
||
|
||
3. DECISIONS MADE
|
||
|
||
${decisionsText}
|
||
|
||
${'─'.repeat(60)}
|
||
|
||
4. ACTION ITEMS
|
||
|
||
${actionsText}
|
||
|
||
${'─'.repeat(60)}
|
||
|
||
5. NEXT STEPS
|
||
|
||
Follow up on all action items as per assigned owners and deadlines listed above.
|
||
|
||
${'═'.repeat(60)}
|
||
These minutes were generated by Nexus One AI and should be reviewed for accuracy before distribution.
|
||
Generated: ${new Date().toLocaleString()}`;
|
||
}
|
||
|
||
// ── Tabs ───────────────────────────────────────────────────────────────────────
|
||
function showTab(name) {
|
||
document.querySelectorAll('.ma-tab').forEach((t,i) => {
|
||
const panels = ['summary','actions','decisions','transcript','minutes'];
|
||
t.classList.toggle('active', panels[i] === name);
|
||
});
|
||
document.querySelectorAll('.ma-tab-panel').forEach(p => p.classList.remove('active'));
|
||
document.getElementById(`tab-${name}`)?.classList.add('active');
|
||
}
|
||
|
||
// ── Export ─────────────────────────────────────────────────────────────────────
|
||
function exportMarkdown() {
|
||
if (!currentResult) return;
|
||
const meta = {
|
||
title: document.getElementById('out-title').textContent,
|
||
meta: document.getElementById('out-meta').textContent,
|
||
};
|
||
const md = `# ${meta.title}\n_${meta.meta}_\n\n## Summary\n${currentResult.summary}\n\n## Decisions\n${(currentResult.decisions||[]).map((d,i)=>`${i+1}. ${d}`).join('\n')}\n\n## Action Items\n${(currentResult.action_items||[]).map(a=>`- **${a.action}** — ${a.owner||'TBD'} · ${a.due||'TBD'}`).join('\n')}\n\n## Topics\n${(currentResult.topics||[]).join(', ')}`;
|
||
const blob = new Blob([md], {type:'text/markdown'});
|
||
const a = document.createElement('a');
|
||
a.href = URL.createObjectURL(blob);
|
||
a.download = `${meta.title.replace(/\s+/g,'-')}-minutes.md`;
|
||
a.click();
|
||
}
|
||
|
||
function exportPDF() {
|
||
const content = document.getElementById('out-minutes').textContent;
|
||
if (!content) return;
|
||
// Open print dialog with minutes content
|
||
const w = window.open('', '_blank');
|
||
w.document.write(`<!DOCTYPE html><html><head><title>Meeting Minutes</title>
|
||
<style>body{font-family:Georgia,serif;max-width:800px;margin:40px auto;line-height:2;color:#111;font-size:14px}
|
||
pre{white-space:pre-wrap;font-family:Georgia,serif;font-size:14px}
|
||
@media print{body{margin:20mm}}</style></head>
|
||
<body><pre>${esc(content)}</pre></body></html>`);
|
||
w.document.close();
|
||
w.print();
|
||
}
|
||
|
||
function copyToClipboard() {
|
||
const txt = document.getElementById('out-minutes').textContent;
|
||
navigator.clipboard.writeText(txt).then(() => showToast('Copied to clipboard'));
|
||
}
|
||
|
||
// ── History ────────────────────────────────────────────────────────────────────
|
||
function saveToHistory(meta, result) {
|
||
const hist = JSON.parse(localStorage.getItem('cezen_meetings')||'[]');
|
||
hist.unshift({ title:meta.title, date:meta.date, ts:new Date().toISOString(), result, meta });
|
||
localStorage.setItem('cezen_meetings', JSON.stringify(hist.slice(0,10)));
|
||
renderHistory();
|
||
}
|
||
|
||
function renderHistory() {
|
||
const hist = JSON.parse(localStorage.getItem('cezen_meetings')||'[]');
|
||
const el = document.getElementById('history-list');
|
||
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>
|
||
<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>
|
||
</div>
|
||
</div>`).join('');
|
||
}
|
||
|
||
function loadHistory(i) {
|
||
const hist = JSON.parse(localStorage.getItem('cezen_meetings')||'[]');
|
||
if (!hist[i]) return;
|
||
const h = hist[i];
|
||
showResult(h.result, h.meta);
|
||
}
|
||
|
||
// ── Helpers ────────────────────────────────────────────────────────────────────
|
||
function formatTranscript(text) {
|
||
return text.split('\n').map(line => {
|
||
const m = line.match(/^([A-Z][a-zA-Z\s]+):\s(.+)/);
|
||
if (m) return `<span class="speaker">${esc(m[1])}:</span> ${esc(m[2])}`;
|
||
return esc(line);
|
||
}).join('\n');
|
||
}
|
||
|
||
function sentimentEmoji(k) {
|
||
return {positive:'😊',neutral:'😐',concern:'😟',negative:'😔',mixed:'🤔'}[k]||'💬';
|
||
}
|
||
|
||
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,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||
}
|
||
|
||
// ── Boot ───────────────────────────────────────────────────────────────────────
|
||
renderHistory();
|
||
</script>
|
||
|
||
<script src="auth.js"></script>
|
||
<script src="branding.js"></script>
|
||
</body>
|
||
</html>
|