/** * doc-picker.js — Document / Audio corpus picker for tool pages. * * Wires the "Select from My Docs" and "Select from My Audio" buttons. * Free-tier (SSO) users see an upgrade modal instead of the picker. * Paid (CaveauAI) users get a searchable multi-select modal backed by * /api/dashboard/documents.php?action=list. */ (function () { 'use strict'; // ── Tier detection ──────────────────────────────────────────────────────── // DBN_FREE_TIER_BALANCE is set only for SSO (free) users. // Paid CaveauAI sessions have it undefined. function isPaidUser() { if (window.DBN_TOOLS_AUTHENTICATED !== true) return false; // CaveauAI sessions never have DBN_FREE_TIER_BALANCE set if (typeof window.DBN_FREE_TIER_BALANCE === 'undefined') return true; // SSO sessions: check the explicit tier variable var t = window.DBN_USER_TIER; return t === 'plus' || t === 'pro'; } // ── Upgrade modal ───────────────────────────────────────────────────────── var upgradeBackdrop = document.getElementById('docPickerUpgradeBackdrop'); function showUpgradeModal() { if (upgradeBackdrop) upgradeBackdrop.hidden = false; } if (upgradeBackdrop) { var upgradeClose = upgradeBackdrop.querySelector('.doc-picker-upgrade-card__dismiss'); if (upgradeClose) { upgradeClose.addEventListener('click', function () { upgradeBackdrop.hidden = true; }); } upgradeBackdrop.addEventListener('click', function (e) { if (e.target === upgradeBackdrop) upgradeBackdrop.hidden = true; }); } // ── Shared picker state ─────────────────────────────────────────────────── var backdrop = document.getElementById('docPickerBackdrop'); var searchEl = backdrop ? backdrop.querySelector('.doc-picker-dialog__search') : null; var listEl = backdrop ? backdrop.querySelector('.doc-picker-list') : null; var countEl = backdrop ? backdrop.querySelector('.doc-picker-dialog__count') : null; var confirmBtn = backdrop ? backdrop.querySelector('.doc-picker-dialog__confirm') : null; var titleEl = backdrop ? backdrop.querySelector('.doc-picker-dialog__head h3') : null; var _allDocs = []; // full list from API var _selected = {}; // { id: { id, title } } var _mode = 'text'; // 'text' or 'audio' var _onConfirm = null; // callback(selected) function openPicker(mode, onConfirm) { _mode = mode || 'text'; _onConfirm = onConfirm; _allDocs = []; if (titleEl) { titleEl.textContent = mode === 'audio' ? 'Select from My Audio' : 'Select from My Docs'; } if (searchEl) searchEl.value = ''; if (listEl) listEl.innerHTML = '

Loading…

'; if (backdrop) backdrop.hidden = false; var url = '/api/dashboard/documents.php?action=list&status=ready&limit=100'; if (mode === 'audio') url += '&source_type=audio'; fetch(url, { credentials: 'same-origin' }) .then(function (r) { return r.json(); }) .then(function (data) { _allDocs = (data.documents || []); renderList(_allDocs); }) .catch(function () { if (listEl) listEl.innerHTML = '

Could not load documents.

'; }); } function renderList(docs) { if (!listEl) return; if (!docs.length) { listEl.innerHTML = '

No documents found.

'; return; } var html = docs.map(function (doc) { var id = doc.id; var sel = !!_selected[id]; var meta = []; if (doc.word_count) meta.push(doc.word_count.toLocaleString() + ' words'); if (doc.chunk_count) meta.push(doc.chunk_count + ' passages'); if (doc.created_at) { try { meta.push(new Date(doc.created_at.replace(' ', 'T') + 'Z') .toLocaleDateString(undefined, { dateStyle: 'medium' })); } catch (_) {} } return '
' + '' + '
' + '
' + esc(doc.title || 'Untitled') + '
' + (meta.length ? '
' + esc(meta.join(' · ')) + '
' : '') + '
' + '
'; }).join(''); listEl.innerHTML = html; listEl.querySelectorAll('.doc-item').forEach(function (el) { el.addEventListener('click', function () { var id = parseInt(el.dataset.id, 10); var doc = _allDocs.find(function (d) { return d.id === id; }); if (!doc) return; if (_selected[id]) { delete _selected[id]; } else { if (_mode === 'audio') { // single-select for audio _selected = {}; } _selected[id] = { id: id, title: doc.title || 'Untitled' }; } var cb = el.querySelector('input[type="checkbox"]'); if (cb) cb.checked = !!_selected[id]; el.classList.toggle('is-selected', !!_selected[id]); el.setAttribute('aria-selected', !!_selected[id]); updateCount(); }); }); updateCount(); } function updateCount() { var n = Object.keys(_selected).length; if (countEl) countEl.textContent = n ? n + ' selected' : ''; if (confirmBtn) confirmBtn.disabled = !n; } function filterList(q) { if (!q) return renderList(_allDocs); var lower = q.toLowerCase(); renderList(_allDocs.filter(function (d) { return (d.title || '').toLowerCase().includes(lower); })); } if (searchEl) { searchEl.addEventListener('input', function () { filterList(searchEl.value.trim()); }); } if (confirmBtn) { confirmBtn.addEventListener('click', function () { var sel = Object.values(_selected); if (backdrop) backdrop.hidden = true; if (_onConfirm) _onConfirm(sel); }); } if (backdrop) { var closeBtn = backdrop.querySelector('.doc-picker-dialog__close'); if (closeBtn) closeBtn.addEventListener('click', function () { backdrop.hidden = true; }); backdrop.addEventListener('click', function (e) { if (e.target === backdrop) backdrop.hidden = true; }); document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && !backdrop.hidden) backdrop.hidden = true; }); } function esc(s) { return String(s).replace(/[&<>"]/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"' }[c]; }); } // ── Chip rendering ──────────────────────────────────────────────────────── function renderChips(chipsEl, items, onRemove) { if (!chipsEl) return; chipsEl.innerHTML = items.map(function (item) { return '' + '' + esc(item.title) + '' + '' + ''; }).join(''); chipsEl.querySelectorAll('.doc-chip__remove').forEach(function (btn) { btn.addEventListener('click', function (e) { e.stopPropagation(); var id = parseInt(btn.closest('.doc-chip').dataset.id, 10); if (onRemove) onRemove(id); }); }); } // ── Text doc picker wiring ──────────────────────────────────────────────── var docPickerBtn = document.getElementById('docPickerBtn'); var docPickerIds = document.getElementById('docPickerIds'); var docPickerChips = document.getElementById('docPickerChips'); var _textSelected = {}; function syncTextPicker() { if (docPickerIds) { docPickerIds.value = Object.keys(_textSelected).join(','); } renderChips(docPickerChips, Object.values(_textSelected), function (id) { delete _textSelected[id]; delete _selected[id]; syncTextPicker(); }); } if (docPickerBtn) { docPickerBtn.addEventListener('click', function () { if (!window.DBN_TOOLS_AUTHENTICATED) return; // shouldn't happen if (!isPaidUser()) { showUpgradeModal(); return; } _selected = Object.assign({}, _textSelected); openPicker('text', function (sel) { _textSelected = {}; sel.forEach(function (s) { _textSelected[s.id] = s; }); syncTextPicker(); }); }); } // ── Audio picker wiring ─────────────────────────────────────────────────── var audioPickerBtn = document.getElementById('audioPickerBtn'); var audioPickerDocId = document.getElementById('audioPickerDocId'); var audioPickerChips = document.getElementById('audioPickerChips'); var _audioSelected = null; // single item or null function syncAudioPicker() { if (audioPickerDocId) { audioPickerDocId.value = _audioSelected ? String(_audioSelected.id) : ''; } renderChips(audioPickerChips, _audioSelected ? [_audioSelected] : [], function () { _audioSelected = null; syncAudioPicker(); }); } if (audioPickerBtn) { audioPickerBtn.addEventListener('click', function () { if (!window.DBN_TOOLS_AUTHENTICATED) return; if (!isPaidUser()) { showUpgradeModal(); return; } _selected = _audioSelected ? { [_audioSelected.id]: _audioSelected } : {}; openPicker('audio', function (sel) { _audioSelected = sel.length ? sel[0] : null; syncAudioPicker(); }); }); } // ── Audio corpus save button (inside transcribe page) ───────────────────── // Allows saving an audio file from the upload queue to the corpus. var audioCorpusSaveBtn = document.getElementById('audioCorpusSaveBtn'); if (audioCorpusSaveBtn && isPaidUser()) { audioCorpusSaveBtn.hidden = false; audioCorpusSaveBtn.addEventListener('click', function () { var audioInput = document.getElementById('audioInput'); if (!audioInput || !audioInput.files.length) { alert('Add an audio file first.'); return; } var file = audioInput.files[0]; var fd = new FormData(); fd.append('audio', file, file.name); audioCorpusSaveBtn.disabled = true; audioCorpusSaveBtn.textContent = 'Saving…'; fetch('/api/dashboard/audio-upload.php', { method: 'POST', credentials: 'same-origin', body: fd }) .then(function (r) { return r.json(); }) .then(function (data) { if (!data.ok) throw new Error(data.error || 'Upload failed'); audioCorpusSaveBtn.textContent = 'Saved ✓'; setTimeout(function () { audioCorpusSaveBtn.textContent = 'Save to My Audio'; audioCorpusSaveBtn.disabled = false; }, 2500); }) .catch(function (err) { alert('Could not save audio: ' + err.message); audioCorpusSaveBtn.textContent = 'Save to My Audio'; audioCorpusSaveBtn.disabled = false; }); }); } }());