const btn = document.getElementById('record-btn'); const statusText = document.getElementById('status-text'); const headerStatus = document.getElementById('header-status'); const instructionsEl = document.getElementById('instructions'); const transcriptList = document.getElementById('transcript-list'); const userChip = document.getElementById('user-chip'); const logoutBtn = document.getElementById('logout-btn'); const modal = document.getElementById('modal'); const modalTitle = document.getElementById('modal-title'); const modalBody = document.getElementById('modal-body'); const modalOpenBtn = document.getElementById('modal-open-btn'); const modalCloseBtn = document.getElementById('modal-close-btn'); let _modalPath = null; let _modalFilename = null; const STATUS_LABELS = { idle: 'Bereit', recording: 'Aufnahme läuft\u2026', processing: 'Wird verarbeitet\u2026', error: 'Fehler', }; // Auth token is stored in sessionStorage so it's gone when the tab closes. // On first load, if no token is present the server will redirect to /login. const token = sessionStorage.getItem('token'); function authHeaders() { return token ? { 'Authorization': `Bearer ${token}` } : {}; } function apiFetch(url, options = {}) { return fetch(url, { ...options, headers: { 'Content-Type': 'application/json', ...authHeaders(), ...(options.headers || {}) }, }); } logoutBtn.addEventListener('click', () => { apiFetch('/logout', { method: 'POST' }).finally(() => { sessionStorage.removeItem('token'); location.href = '/login'; }); }); function openModal(filename, path) { _modalPath = path; _modalFilename = filename; modalTitle.textContent = filename.replace(/\.md$/, '').replace(/^\d{4}-\d{2}-\d{2}-\d{4}-/, ''); modalBody.innerHTML = ''; modal.classList.remove('hidden'); apiFetch(`/transcripts/${encodeURIComponent(filename)}`) .then(r => r.text()) .then(md => { modalBody.innerHTML = DOMPurify.sanitize(marked.parse(md)); }); } function closeModal() { modal.classList.add('hidden'); _modalPath = null; _modalFilename = null; } modalCloseBtn.addEventListener('click', closeModal); modal.querySelector('.modal-backdrop').addEventListener('click', closeModal); document.addEventListener('keydown', e => { if (e.key === 'Escape') closeModal(); }); modalOpenBtn.addEventListener('click', () => { if (_modalPath) apiFetch('/open', { method: 'POST', body: JSON.stringify({ path: _modalPath }) }); }); instructionsEl.addEventListener('input', async () => { await apiFetch('/instructions', { method: 'POST', body: JSON.stringify({ instructions: instructionsEl.value }), }); }); function setStatus(status) { btn.className = status; headerStatus.className = `status-badge ${status}`; const label = STATUS_LABELS[status] || status; statusText.textContent = status === 'error' ? label + ' — klicken zum Zurücksetzen' : label; headerStatus.textContent = label; btn.disabled = status === 'processing'; } btn.addEventListener('click', async () => { const r = await apiFetch('/toggle', { method: 'POST' }); const data = await r.json(); if (data.action === 'started') { setStatus('recording'); } else if (data.action === 'reset') { setStatus('idle'); } }); function connectWs() { const proto = location.protocol === 'https:' ? 'wss:' : 'ws:'; const ws = new WebSocket(`${proto}//${location.host}/ws?token=${encodeURIComponent(token || '')}`); ws.onmessage = (e) => { const msg = JSON.parse(e.data); if (msg.event === 'processing') setStatus('processing'); if (msg.event === 'saved') { setStatus('idle'); loadTranscripts(); } if (msg.event === 'error') { setStatus('error'); } }; ws.onclose = () => setTimeout(connectWs, 2000); } async function loadTranscripts() { const r = await apiFetch('/transcripts'); if (!r.ok) return; const items = await r.json(); transcriptList.replaceChildren( ...items.map((t) => { const div = document.createElement('div'); div.className = 'transcript-item'; const name = document.createElement('span'); name.className = 'name'; name.textContent = t.filename.replace(/\.md$/, '').replace(/^\d{4}-\d{2}-\d{2}-\d{4}-/, ''); const meta = document.createElement('span'); meta.className = 'meta'; meta.textContent = `${Math.round(t.size / 1024 * 10) / 10} KB`; div.addEventListener('click', () => openModal(t.filename, t.path)); const reprocessBtn = document.createElement('button'); reprocessBtn.className = 'del-btn'; reprocessBtn.title = 'Neu verarbeiten'; reprocessBtn.innerHTML = ''; reprocessBtn.addEventListener('click', async (e) => { e.stopPropagation(); reprocessBtn.disabled = true; await apiFetch(`/transcripts/${encodeURIComponent(t.filename)}/reprocess`, { method: 'POST', body: JSON.stringify({ instructions: instructionsEl.value }), }); reprocessBtn.disabled = false; loadTranscripts(); }); const delBtn = document.createElement('button'); delBtn.className = 'del-btn'; delBtn.title = 'Löschen'; delBtn.innerHTML = ''; delBtn.addEventListener('click', async (e) => { e.stopPropagation(); await apiFetch(`/transcripts/${encodeURIComponent(t.filename)}`, { method: 'DELETE' }); loadTranscripts(); }); const actions = document.createElement('div'); actions.className = 'item-actions'; actions.append(reprocessBtn, delBtn); div.append(name, meta, actions); return div; }) ); } (async () => { const r = await apiFetch('/status'); if (r.status === 401) { location.href = '/login'; return; } const data = await r.json(); setStatus(data.status); if (data.username) { userChip.textContent = data.username; } connectWs(); loadTranscripts(); })();