feat(dashboard): export current view
This commit is contained in:
@@ -34,6 +34,11 @@
|
||||
<option value="rejected">Rejected</option>
|
||||
<option value="errors">Errors</option>
|
||||
</select>
|
||||
<select id="exportFormat" title="Export format">
|
||||
<option value="jsonl">JSONL</option>
|
||||
<option value="csv">CSV</option>
|
||||
</select>
|
||||
<button class="btn-export" onclick="exportCurrent()">Export</button>
|
||||
</div>
|
||||
|
||||
<!-- New Engram -->
|
||||
@@ -167,14 +172,7 @@ function setView(view) {
|
||||
}
|
||||
|
||||
async function loadCards() {
|
||||
let url = `/api/engrams?limit=${state.limit}&offset=${state.offset}`;
|
||||
if (state.search) url += `&search=${encodeURIComponent(state.search)}`;
|
||||
if (state.filter === 'confirmed') url += '&confirmed=1';
|
||||
if (state.filter === 'pending') url += '&confirmed=0';
|
||||
if (state.filter === 'rejected') url += '&verdict=confirmed_false';
|
||||
if (state.filter === 'errors') url += '&tag=error';
|
||||
|
||||
const data = await api(url);
|
||||
const data = await api(buildEngramsUrl(state.limit, state.offset));
|
||||
state.items = data.items;
|
||||
renderCardsWithSuggestions();
|
||||
document.getElementById('pageNum').textContent = Math.floor(state.offset / state.limit) + 1;
|
||||
@@ -182,6 +180,68 @@ async function loadCards() {
|
||||
document.getElementById('btnNext').disabled = data.items.length < state.limit;
|
||||
}
|
||||
|
||||
function buildEngramsUrl(limit, offset) {
|
||||
let url = `/api/engrams?limit=${limit}&offset=${offset}`;
|
||||
if (state.search) url += `&search=${encodeURIComponent(state.search)}`;
|
||||
if (state.filter === 'confirmed') url += '&confirmed=1';
|
||||
if (state.filter === 'pending') url += '&confirmed=0';
|
||||
if (state.filter === 'rejected') url += '&verdict=confirmed_false';
|
||||
if (state.filter === 'errors') url += '&tag=error';
|
||||
return url;
|
||||
}
|
||||
|
||||
async function exportCurrent() {
|
||||
const fmt = (document.getElementById('exportFormat')?.value || 'jsonl').toLowerCase();
|
||||
const limit = 100;
|
||||
const max = 5000;
|
||||
let offset = 0;
|
||||
let all = [];
|
||||
while (all.length < max) {
|
||||
const data = await api(buildEngramsUrl(limit, offset));
|
||||
const items = data.items || [];
|
||||
all = all.concat(items);
|
||||
if (items.length < limit) break;
|
||||
offset += limit;
|
||||
}
|
||||
|
||||
const safe = (s) => String(s ?? '').replace(/[\\r\\n]+/g, ' ').trim();
|
||||
let payload = '';
|
||||
let mime = 'text/plain';
|
||||
if (fmt === 'csv') {
|
||||
mime = 'text/csv';
|
||||
const esc = (v) => '\"' + String(v ?? '').replace(/\"/g, '\"\"') + '\"';
|
||||
payload += ['id','created','source','confidence','verdict','tags','content'].join(',') + '\\n';
|
||||
for (const it of all) {
|
||||
payload += [
|
||||
esc(it.id),
|
||||
esc(it.created),
|
||||
esc(it.source),
|
||||
esc(it.confidence),
|
||||
esc(it.verdict),
|
||||
esc((it.tags || []).join('|')),
|
||||
esc((it.content || '').replace(/\\r?\\n/g, '\\\\n')),
|
||||
].join(',') + '\\n';
|
||||
}
|
||||
} else {
|
||||
mime = 'application/x-ndjson';
|
||||
payload = all.map(x => JSON.stringify(x)).join('\\n') + (all.length ? '\\n' : '');
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const ymd = `${now.getFullYear()}-${String(now.getMonth()+1).padStart(2,'0')}-${String(now.getDate()).padStart(2,'0')}`;
|
||||
const filename = `second-brain_${ymd}_${safe(state.filter || 'all')}_${fmt}.${fmt}`;
|
||||
|
||||
const blob = new Blob([payload], {type: mime});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
||||
}
|
||||
|
||||
async function loadStatus() {
|
||||
const reqs = await Promise.allSettled([
|
||||
api('/api/config'),
|
||||
|
||||
Reference in New Issue
Block a user