Implement lazy audio loading to fix large response truncation
This commit is contained in:
111
static/js/app.js
111
static/js/app.js
@@ -1,7 +1,6 @@
|
||||
/**
|
||||
* Audiobook Maker Pro v4.2 - Main Application
|
||||
* UPDATED: Publishing support, thumbnails, sections terminology
|
||||
* FIXED: Edit/rename in new archive UI, republish populates existing data
|
||||
* UPDATED: Lazy audio loading to avoid large response truncation
|
||||
*/
|
||||
|
||||
// ============================================
|
||||
@@ -20,7 +19,7 @@ let ttsEditModal = null;
|
||||
let publishModal = null;
|
||||
let publishingProjectId = null;
|
||||
let currentWorkflowStage = 'upload';
|
||||
let allArchiveProjects = []; // Cache for republish dialog
|
||||
let allArchiveProjects = [];
|
||||
|
||||
// ============================================
|
||||
// Initialization
|
||||
@@ -398,7 +397,7 @@ function updateWorkflowProgress(stage) {
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Helper: Switch to Editor Tab
|
||||
// Helpers
|
||||
// ============================================
|
||||
|
||||
function switchToEditorTab() {
|
||||
@@ -409,10 +408,6 @@ function switchToEditorTab() {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Helper: Start from scratch
|
||||
// ============================================
|
||||
|
||||
function startFromScratch() {
|
||||
document.getElementById('uploadSection').style.display = 'none';
|
||||
document.getElementById('editorSection').style.display = 'block';
|
||||
@@ -432,10 +427,6 @@ function startFromScratch() {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Loading Overlay
|
||||
// ============================================
|
||||
|
||||
function showLoader(text = 'Processing...', subtext = 'Please wait') {
|
||||
const overlay = document.getElementById('loadingOverlay');
|
||||
if(overlay) {
|
||||
@@ -591,7 +582,6 @@ async function openProjectArchive() {
|
||||
const response = await fetch('/api/projects');
|
||||
const data = await response.json();
|
||||
|
||||
// Cache for republish dialog
|
||||
allArchiveProjects = data.projects || [];
|
||||
|
||||
const container = document.getElementById('projectList');
|
||||
@@ -616,7 +606,6 @@ async function openProjectArchive() {
|
||||
: '';
|
||||
|
||||
const canPublish = project.audio_count > 0;
|
||||
const safeNameAttr = escapeHtml(project.name).replace(/'/g, "'");
|
||||
|
||||
return `
|
||||
<div class="project-item-v2" id="project-item-${project.id}">
|
||||
@@ -694,7 +683,7 @@ async function openProjectArchive() {
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Rename Functionality (FIXED for new UI)
|
||||
// Rename
|
||||
// ============================================
|
||||
|
||||
function startEditProjectName(projectId) {
|
||||
@@ -755,18 +744,15 @@ async function saveProjectName(projectId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the text in the cached element
|
||||
const textEl = document.getElementById(`project-name-text-${projectId}`);
|
||||
if (textEl) textEl.textContent = newName;
|
||||
|
||||
// Update cached project list
|
||||
const cached = allArchiveProjects.find(p => p.id === projectId);
|
||||
if (cached) cached.name = newName;
|
||||
|
||||
cancelEditProjectName(projectId);
|
||||
showNotification('Project renamed successfully', 'success');
|
||||
|
||||
// Update header if currently loaded project was renamed
|
||||
if (currentProject.id === projectId) {
|
||||
currentProject.name = newName;
|
||||
const nameInput = document.getElementById('projectName');
|
||||
@@ -820,13 +806,12 @@ async function uploadThumbnail(projectId, inputEl) {
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Publishing Functions (FIXED to populate existing data)
|
||||
// Publishing
|
||||
// ============================================
|
||||
|
||||
function openPublishDialog(projectId) {
|
||||
publishingProjectId = projectId;
|
||||
|
||||
// Find project in cache to populate existing data
|
||||
const project = allArchiveProjects.find(p => p.id === projectId);
|
||||
|
||||
if (!publishModal) {
|
||||
@@ -871,7 +856,6 @@ function openPublishDialog(projectId) {
|
||||
publishModal = new bootstrap.Modal(document.getElementById('publishModal'));
|
||||
}
|
||||
|
||||
// Populate with existing data (FIX: was always empty before)
|
||||
document.getElementById('pub-name').value = project ? project.name : '';
|
||||
document.getElementById('pub-author').value = project ? (project.author || '') : '';
|
||||
document.getElementById('pub-description').value = project ? (project.description || '') : '';
|
||||
@@ -926,14 +910,15 @@ async function unpublishProject(projectId) {
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Project Load / Delete
|
||||
// Project Load / Delete - LAZY AUDIO LOADING
|
||||
// ============================================
|
||||
|
||||
async function loadProject(projectId) {
|
||||
showLoader('Loading project...');
|
||||
showLoader('Loading project...', 'Fetching metadata');
|
||||
if(archiveModal) archiveModal.hide();
|
||||
|
||||
try {
|
||||
// Step 1: Load lightweight metadata (no audio_data)
|
||||
const response = await fetch(`/api/projects/${projectId}`);
|
||||
const data = await response.json();
|
||||
|
||||
@@ -955,27 +940,93 @@ async function loadProject(projectId) {
|
||||
renderProjectInEditor(data);
|
||||
}
|
||||
|
||||
let hasAudio = false;
|
||||
// Step 2: Find blocks that need audio fetched
|
||||
const audioBlocks = [];
|
||||
for (const ch of data.chapters) {
|
||||
for (const bl of ch.blocks) {
|
||||
if (bl.audio_data && bl.block_type !== 'image') {
|
||||
hasAudio = true;
|
||||
break;
|
||||
if (bl.has_audio && bl.block_type !== 'image') {
|
||||
audioBlocks.push(bl.id);
|
||||
}
|
||||
}
|
||||
if (hasAudio) break;
|
||||
}
|
||||
updateWorkflowProgress(hasAudio ? 'audio-ready' : 'edit');
|
||||
|
||||
updateWorkflowProgress(audioBlocks.length > 0 ? 'audio-ready' : 'edit');
|
||||
|
||||
hideLoader();
|
||||
showNotification('Project loaded successfully!', 'success');
|
||||
|
||||
if (audioBlocks.length === 0) {
|
||||
showNotification('Project loaded successfully!', 'success');
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 3: Lazy-load audio in background, parallel batches
|
||||
showNotification(`Project loaded. Fetching ${audioBlocks.length} audio blocks in background...`, 'info');
|
||||
loadAudioBlocksInBackground(projectId, audioBlocks);
|
||||
|
||||
} catch (error) {
|
||||
hideLoader();
|
||||
console.error('Load project error:', error);
|
||||
alert('Failed to load project: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function loadAudioBlocksInBackground(projectId, blockIds) {
|
||||
const BATCH_SIZE = 5;
|
||||
let loaded = 0;
|
||||
let failed = 0;
|
||||
|
||||
async function fetchOne(blockId) {
|
||||
try {
|
||||
const resp = await fetch(`/api/projects/${projectId}/audio/${blockId}`);
|
||||
const data = await resp.json();
|
||||
|
||||
if (data.error || !data.audio_data) {
|
||||
failed++;
|
||||
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;
|
||||
|
||||
// Update DOM indicator (green dot)
|
||||
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 loaded';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loaded++;
|
||||
} catch (e) {
|
||||
failed++;
|
||||
console.warn(`Failed to load audio for block ${blockId}:`, e);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < blockIds.length; i += BATCH_SIZE) {
|
||||
const batch = blockIds.slice(i, i + BATCH_SIZE);
|
||||
await Promise.all(batch.map(fetchOne));
|
||||
}
|
||||
|
||||
const msg = failed > 0
|
||||
? `Loaded ${loaded}/${blockIds.length} audio blocks (${failed} failed)`
|
||||
: `All ${loaded} audio blocks loaded ✓`;
|
||||
showNotification(msg, failed > 0 ? 'warning' : 'success');
|
||||
|
||||
if (typeof updatePanelUI === 'function') {
|
||||
updatePanelUI();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function deleteProject(projectId) {
|
||||
if (!confirm('Are you sure you want to delete this project? This action cannot be undone.')) return;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user