Show PDF instantly from local file, extract text in background

- PDF renders immediately on file select via blob URL (no server round-trip)
- Text extraction happens in background (only needed for TTS audio)
- Default view is now PDF (shows the real document, not extracted text)
- User can switch to Text view to see extracted sentences with highlighting
- Nextcloud PDFs also default to PDF view when opened

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ifedan-ed 2026-04-11 04:56:30 +02:00
parent 7afbe3a22a
commit 4fc76bc650

View file

@ -279,15 +279,13 @@ async function loadDoc(text, title = 'Document', docId = null, startIdx = 0, pdf
if (pdfB64) {
viewToggle.style.display = '';
state.sentencePage = buildSentencePageMap(pageTexts, state.sentences);
// Render asynchronously so the text view appears immediately
renderPDF(pdfB64).catch(console.error);
// Default to text view
setView('text');
renderPDF(pdfB64);
setView('pdf'); // default to real PDF view
} else {
viewToggle.style.display = 'none';
setView('text');
state.sentencePage = [];
pdfDoc = null;
if (pdfBlobUrl) { URL.revokeObjectURL(pdfBlobUrl); pdfBlobUrl = null; }
$('pdf-pages').innerHTML = '';
}
@ -583,25 +581,67 @@ uploadArea.addEventListener('dragleave', () => uploadArea.classList.remove('drag
uploadArea.addEventListener('drop', e => {
e.preventDefault(); uploadArea.classList.remove('drag-over');
const f = e.dataTransfer.files[0];
if (f?.type === 'application/pdf') uploadPDF(f);
if (f?.type === 'application/pdf') handlePDF(f);
});
pdfInput.addEventListener('change', () => { if (pdfInput.files[0]) uploadPDF(pdfInput.files[0]); });
pdfInput.addEventListener('change', () => { if (pdfInput.files[0]) handlePDF(pdfInput.files[0]); });
async function uploadPDF(file) {
async function handlePDF(file) {
const status = $('pdf-status');
showLoading('Extracting PDF…');
const form = new FormData();
form.append('file', file);
const title = file.name.replace(/\.pdf$/i, '');
// ── Step 1: show PDF instantly from local file, no server needed ──
if (pdfBlobUrl) URL.revokeObjectURL(pdfBlobUrl);
pdfBlobUrl = URL.createObjectURL(file);
// Show header + PDF view right away
$('document-header').style.display = '';
$('document-title').textContent = title;
$('document-meta').textContent = 'Extracting text for playback…';
$('view-toggle').style.display = '';
$('pdf-pages').innerHTML = '';
const iframe = document.createElement('iframe');
iframe.id = 'pdf-iframe';
iframe.src = pdfBlobUrl;
iframe.style.cssText = 'width:100%;height:100%;border:none;display:block;';
$('pdf-pages').appendChild(iframe);
setView('pdf'); // ← show the real PDF immediately
nowPlaying.textContent = 'Extracting text for playback…';
status.textContent = 'Extracting…';
// ── Step 2: extract text in background (needed only for TTS) ──
try {
const form = new FormData();
form.append('file', file);
const resp = await fetch('/api/extract-pdf', { method: 'POST', body: form });
if (!resp.ok) throw new Error((await resp.json()).detail || 'Failed');
const data = await resp.json();
await loadDoc(data.text, data.title || file.name, null, 0, data.pdf_b64, data.page_texts || []);
status.textContent = '✓ Loaded';
// Update state with extracted sentences (keep PDF view active)
state.sentences = splitSentences(data.text);
state.docTitle = data.title || title;
state.currentIdx = 0;
state.docId = null;
state.pageTexts = data.page_texts || [];
state.sentencePage = buildSentencePageMap(state.pageTexts, state.sentences);
audioCache.clear();
const real = state.sentences.filter(s => !s.isPara).length;
$('document-meta').textContent = `${real} sentences · ready`;
$('document-title').textContent = data.title || title;
totSent.textContent = real;
curSent.textContent = 0;
progressFill.style.width = '0%';
nowPlaying.textContent = 'Ready — press play';
// Also render text view in background for when user switches
renderText(state.sentences);
textViewer.style.fontSize = `${state.fontSize}px`;
status.textContent = '✓ Ready';
} catch (e) {
status.textContent = '✗ ' + e.message;
} finally {
hideLoading();
nowPlaying.textContent = 'Text extraction failed';
}
}