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:
parent
7afbe3a22a
commit
4fc76bc650
1 changed files with 55 additions and 15 deletions
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue