v4.3: file-based media storage + manual VACUUM maintenance
This commit is contained in:
148
static/js/app.js
148
static/js/app.js
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Audiobook Maker Pro v4.2 - Main Application
|
||||
* UPDATED: Lazy audio loading to avoid large response truncation
|
||||
* Audiobook Maker Pro v4.3 - Main Application
|
||||
* UPDATED: Lazy audio loading + Storage & Maintenance (VACUUM)
|
||||
*/
|
||||
|
||||
// ============================================
|
||||
@@ -17,6 +17,7 @@ let voices = [];
|
||||
let archiveModal = null;
|
||||
let ttsEditModal = null;
|
||||
let publishModal = null;
|
||||
let dbMaintenanceModal = null;
|
||||
let publishingProjectId = null;
|
||||
let currentWorkflowStage = 'upload';
|
||||
let allArchiveProjects = [];
|
||||
@@ -26,7 +27,7 @@ let allArchiveProjects = [];
|
||||
// ============================================
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('🎧 Audiobook Maker Pro v4.2 initializing...');
|
||||
console.log('🎧 Audiobook Maker Pro v4.3 initializing...');
|
||||
|
||||
archiveModal = new bootstrap.Modal(document.getElementById('archiveModal'));
|
||||
ttsEditModal = new bootstrap.Modal(document.getElementById('ttsEditModal'));
|
||||
@@ -979,6 +980,32 @@ async function loadAudioBlocksInBackground(projectId, blockIds) {
|
||||
async function fetchOne(blockId) {
|
||||
try {
|
||||
const resp = await fetch(`/api/projects/${projectId}/audio/${blockId}`);
|
||||
|
||||
// v4.3: এন্ডপয়েন্ট বাইনারি অডিও (audio/*) অথবা legacy base64 JSON দিতে পারে
|
||||
const contentType = resp.headers.get('content-type') || '';
|
||||
|
||||
const blockData = editorBlocks.find(b => b.db_id === blockId);
|
||||
|
||||
if (contentType.startsWith('audio/')) {
|
||||
// নতুন: ফাইল আছে — শুধু indicator আপডেট করি, base64 মেমরিতে রাখি না
|
||||
// (reader নিজেই lazy fetch করবে)
|
||||
if (blockData) {
|
||||
blockData.has_audio = true;
|
||||
const blockEl = document.getElementById(blockData.id);
|
||||
if (blockEl) {
|
||||
const indicator = blockEl.querySelector('.audio-indicator');
|
||||
if (indicator) {
|
||||
indicator.classList.remove('no-audio');
|
||||
indicator.classList.add('has-audio');
|
||||
indicator.title = 'Audio available';
|
||||
}
|
||||
}
|
||||
}
|
||||
loaded++;
|
||||
return;
|
||||
}
|
||||
|
||||
// Legacy base64 JSON
|
||||
const data = await resp.json();
|
||||
|
||||
if (data.error || !data.audio_data) {
|
||||
@@ -986,13 +1013,11 @@ async function loadAudioBlocksInBackground(projectId, blockIds) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update editorBlocks state by db_id
|
||||
const blockData = editorBlocks.find(b => b.db_id === blockId);
|
||||
if (blockData) {
|
||||
blockData.audio_data = data.audio_data;
|
||||
blockData.audio_format = data.audio_format;
|
||||
blockData.has_audio = true;
|
||||
|
||||
// Update DOM indicator (green dot)
|
||||
const blockEl = document.getElementById(blockData.id);
|
||||
if (blockEl) {
|
||||
const indicator = blockEl.querySelector('.audio-indicator');
|
||||
@@ -1017,8 +1042,8 @@ async function loadAudioBlocksInBackground(projectId, blockIds) {
|
||||
}
|
||||
|
||||
const msg = failed > 0
|
||||
? `Loaded ${loaded}/${blockIds.length} audio blocks (${failed} failed)`
|
||||
: `All ${loaded} audio blocks loaded ✓`;
|
||||
? `Verified ${loaded}/${blockIds.length} audio blocks (${failed} failed)`
|
||||
: `All ${loaded} audio blocks verified ✓`;
|
||||
showNotification(msg, failed > 0 ? 'warning' : 'success');
|
||||
|
||||
if (typeof updatePanelUI === 'function') {
|
||||
@@ -1028,7 +1053,7 @@ async function loadAudioBlocksInBackground(projectId, blockIds) {
|
||||
|
||||
|
||||
async function deleteProject(projectId) {
|
||||
if (!confirm('Are you sure you want to delete this project? This action cannot be undone.')) return;
|
||||
if (!confirm('Are you sure you want to delete this project? This action cannot be undone.\n\nএই প্রজেক্টের অডিও ও ইমেজ ফাইলগুলোও মুছে যাবে।')) return;
|
||||
|
||||
showLoader('Deleting...');
|
||||
|
||||
@@ -1046,6 +1071,111 @@ async function deleteProject(projectId) {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// v4.3: Storage & Maintenance (VACUUM)
|
||||
// ============================================
|
||||
|
||||
function openDbMaintenance() {
|
||||
if (!dbMaintenanceModal) {
|
||||
dbMaintenanceModal = new bootstrap.Modal(document.getElementById('dbMaintenanceModal'));
|
||||
}
|
||||
dbMaintenanceModal.show();
|
||||
loadDbStats();
|
||||
}
|
||||
|
||||
async function loadDbStats() {
|
||||
const loadingEl = document.getElementById('dbStatsLoading');
|
||||
const contentEl = document.getElementById('dbStatsContent');
|
||||
if (loadingEl) loadingEl.style.display = 'block';
|
||||
if (contentEl) contentEl.style.display = 'none';
|
||||
|
||||
try {
|
||||
const resp = await fetch('/api/maintenance/db-stats');
|
||||
const s = await resp.json();
|
||||
|
||||
if (s.error) {
|
||||
showNotification(s.error, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('dbmFileSize').textContent = `${s.file_size_mb} MB`;
|
||||
document.getElementById('dbmMediaSize').textContent = `${s.media_size_mb} MB`;
|
||||
document.getElementById('dbmFreeText').textContent =
|
||||
`${s.free_mb} MB (${s.free_percent}%)`;
|
||||
|
||||
const bar = document.getElementById('dbmFreeBar');
|
||||
const pct = Math.min(s.free_percent, 100);
|
||||
bar.style.width = pct + '%';
|
||||
bar.textContent = `${s.free_percent}%`;
|
||||
|
||||
// রঙ: কম হলে সবুজ, বেশি হলে হলুদ/লাল
|
||||
bar.className = 'progress-bar';
|
||||
if (s.free_percent >= 30) {
|
||||
bar.classList.add('bg-danger');
|
||||
} else if (s.free_percent >= 15) {
|
||||
bar.classList.add('bg-warning');
|
||||
} else {
|
||||
bar.classList.add('bg-success');
|
||||
}
|
||||
|
||||
const advice = document.getElementById('dbmAdvice');
|
||||
if (s.free_percent >= 15) {
|
||||
advice.className = 'alert alert-warning';
|
||||
advice.style.display = 'block';
|
||||
advice.innerHTML = `<i class="bi bi-exclamation-triangle me-1"></i>` +
|
||||
`ডেটাবেসে <strong>${s.free_percent}%</strong> ফাঁকা স্পেস জমেছে। ` +
|
||||
`<strong>Run VACUUM</strong> চালিয়ে এটি reclaim করতে পারেন।`;
|
||||
} else {
|
||||
advice.className = 'alert alert-success';
|
||||
advice.style.display = 'block';
|
||||
advice.innerHTML = `<i class="bi bi-check-circle me-1"></i>` +
|
||||
`ফাঁকা স্পেস কম (<strong>${s.free_percent}%</strong>) — এখন VACUUM চালানোর দরকার নেই।`;
|
||||
}
|
||||
|
||||
if (loadingEl) loadingEl.style.display = 'none';
|
||||
if (contentEl) contentEl.style.display = 'block';
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
showNotification('Failed to load storage info', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function runDbVacuum() {
|
||||
const vacuumBtn = document.getElementById('dbmVacuumBtn');
|
||||
const refreshBtn = document.getElementById('dbmRefreshBtn');
|
||||
|
||||
if (!confirm('VACUUM এখন চালাবেন? এটি ডেটাবেস ছোট করবে কিন্তু কিছু সময় (ডেটাবেস বড় হলে কয়েক মিনিট) নিতে পারে।')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (vacuumBtn) {
|
||||
vacuumBtn.disabled = true;
|
||||
vacuumBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Running...';
|
||||
}
|
||||
if (refreshBtn) refreshBtn.disabled = true;
|
||||
|
||||
try {
|
||||
const resp = await fetch('/api/maintenance/vacuum', { method: 'POST' });
|
||||
const data = await resp.json();
|
||||
|
||||
if (data.error) {
|
||||
showNotification(data.error, 'error');
|
||||
} else {
|
||||
showNotification(data.message || 'VACUUM complete', 'success');
|
||||
loadDbStats();
|
||||
}
|
||||
} catch (e) {
|
||||
showNotification('VACUUM failed', 'error');
|
||||
} finally {
|
||||
if (vacuumBtn) {
|
||||
vacuumBtn.disabled = false;
|
||||
vacuumBtn.innerHTML = '<i class="bi bi-stars me-1"></i>Run VACUUM';
|
||||
}
|
||||
if (refreshBtn) refreshBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// TTS Text Editing
|
||||
// ============================================
|
||||
|
||||
Reference in New Issue
Block a user