Cancel PDF reading and OCR when the offer assistant closes
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
/** Stop awaiting work on cancellation; retain handlers for late settlement. */
|
||||
export function awaitWithAbort(work, signal, cancel) {
|
||||
if (!signal) return Promise.resolve(work);
|
||||
return new Promise((resolve,reject)=>{
|
||||
let settled=false;
|
||||
const cleanup=()=>signal.removeEventListener('abort',abort);
|
||||
const abort=()=>{
|
||||
if(settled)return;
|
||||
settled=true;cleanup();
|
||||
try {Promise.resolve(cancel?.()).catch(()=>{});} catch { /* cancellation is best effort */ }
|
||||
reject(signal.reason || new DOMException('İşlem iptal edildi.','AbortError'));
|
||||
};
|
||||
Promise.resolve(work).then(value=>{
|
||||
if(settled)return;
|
||||
if(signal.aborted){abort();return;}
|
||||
settled=true;cleanup();resolve(value);
|
||||
},error=>{
|
||||
if(settled)return;
|
||||
settled=true;cleanup();reject(signal.aborted ? signal.reason : error);
|
||||
});
|
||||
if(signal.aborted)abort();else signal.addEventListener('abort',abort,{once:true});
|
||||
});
|
||||
}
|
||||
@@ -1,19 +1,23 @@
|
||||
import {awaitWithAbort} from './abortable.js';
|
||||
/** Read every requested page in order, retaining text/OCR provenance per page. */
|
||||
export async function readPdfPages(doc, {maxPages=Infinity, ocrPage, forceOcr=false, onProgress}={}) {
|
||||
export async function readPdfPages(doc, {maxPages=Infinity, ocrPage, forceOcr=false, onProgress, signal}={}) {
|
||||
if (!(maxPages === Infinity || (Number.isInteger(maxPages) && maxPages > 0))) throw new Error('Geçersiz sayfa sınırı.');
|
||||
signal?.throwIfAborted();
|
||||
const pages = Math.min(doc.numPages,maxPages);
|
||||
const results=[];
|
||||
for(let number=1;number<=pages;number++) {
|
||||
signal?.throwIfAborted();
|
||||
onProgress?.({sayfa:number,toplam:pages});
|
||||
const page=await doc.getPage(number);
|
||||
const page=await awaitWithAbort(doc.getPage(number).then(page=>{if(signal?.aborted){page.cleanup?.();signal.throwIfAborted();}return page;}),signal);
|
||||
try {
|
||||
const content=await page.getTextContent();
|
||||
const content=await awaitWithAbort(page.getTextContent(),signal);
|
||||
let text=content.items.map(item=>item.str + (item.hasEOL ? '\n' : ' ')).join('').trim();
|
||||
let method='text';
|
||||
if(ocrPage && (forceOcr || text.replace(/\s/g,'').length < 100)) {
|
||||
try { text=await ocrPage(page); method='ocr'; }
|
||||
catch { throw new Error(`${number}. sayfada OCR tamamlanamadı. Okunabilir belgeyle yeniden deneyin.`); }
|
||||
try { text=await awaitWithAbort(ocrPage(page),signal); method='ocr'; }
|
||||
catch { signal?.throwIfAborted(); throw new Error(`${number}. sayfada OCR tamamlanamadı. Okunabilir belgeyle yeniden deneyin.`); }
|
||||
}
|
||||
signal?.throwIfAborted();
|
||||
results.push({page:number,text,method});
|
||||
} finally { page.cleanup?.(); }
|
||||
}
|
||||
|
||||
+38
-19
@@ -1,52 +1,71 @@
|
||||
import {awaitWithAbort} from './abortable.js';
|
||||
import { readPdfPages } from './pdfDocumentReader.js';
|
||||
|
||||
async function openPdf(file) {
|
||||
async function openPdf(file,signal) {
|
||||
signal?.throwIfAborted();
|
||||
const pdfjs = await import('pdfjs-dist');
|
||||
pdfjs.GlobalWorkerOptions.workerSrc = (await import('pdfjs-dist/build/pdf.worker.min.mjs?url')).default;
|
||||
let task;
|
||||
let task,destruction;
|
||||
const destroy=()=>destruction ||= task?.destroy();
|
||||
try {
|
||||
task = pdfjs.getDocument({data:await file.arrayBuffer()});
|
||||
return {doc:await task.promise,task};
|
||||
const data=await awaitWithAbort(file.arrayBuffer(),signal);
|
||||
signal?.throwIfAborted();
|
||||
task = pdfjs.getDocument({data});
|
||||
return {doc:await awaitWithAbort(task.promise,signal,destroy),task:{destroy}};
|
||||
}
|
||||
catch(error) {
|
||||
await task?.destroy();
|
||||
await destroy();
|
||||
signal?.throwIfAborted();
|
||||
if(error.name === 'InvalidPDFException') throw new Error('PDF dosyası bozuk veya geçerli PDF biçiminde değil.');
|
||||
if(error.name === 'PasswordException') throw new Error('PDF parola korumalı; okunabilir bir kopyasını yükleyin.');
|
||||
throw new Error('PDF okunamadı. Dosyayı kontrol edip yeniden deneyin.');
|
||||
}
|
||||
}
|
||||
|
||||
async function readPdf(file,{ocr=false,forceOcr=false,maxPages=Infinity,onProgress}={}) {
|
||||
const {doc,task}=await openPdf(file);
|
||||
let worker;
|
||||
async function readPdf(file,{ocr=false,forceOcr=false,maxPages=Infinity,onProgress,signal}={}) {
|
||||
const {doc,task}=await openPdf(file,signal);
|
||||
let worker,termination,render;
|
||||
const stopWorker=()=>termination ||= worker?.terminate();
|
||||
const cancel=()=>{render?.cancel();void Promise.resolve(stopWorker()).catch(()=>{});void Promise.resolve(task.destroy()).catch(()=>{});};
|
||||
signal?.addEventListener('abort',cancel,{once:true});
|
||||
try {
|
||||
const result=await readPdfPages(doc,{maxPages,forceOcr,onProgress,ocrPage:ocr ? async(page)=>{
|
||||
signal?.throwIfAborted();
|
||||
const result=await readPdfPages(doc,{maxPages,forceOcr,onProgress,signal,ocrPage:ocr ? async(page)=>{
|
||||
if(!worker) {
|
||||
const {createWorker}=await import('tesseract.js');
|
||||
worker=await createWorker('tur');
|
||||
signal?.throwIfAborted();
|
||||
worker=await awaitWithAbort(createWorker('tur').then(created=>{
|
||||
if(signal?.aborted){void created.terminate().catch(()=>{});signal.throwIfAborted();}
|
||||
return created;
|
||||
}),signal);
|
||||
}
|
||||
const viewport=page.getViewport({scale:2});
|
||||
const canvas=document.createElement('canvas');
|
||||
canvas.width=viewport.width;canvas.height=viewport.height;
|
||||
try {
|
||||
await page.render({canvasContext:canvas.getContext('2d'),viewport}).promise;
|
||||
return (await worker.recognize(canvas)).data.text;
|
||||
} finally { canvas.width=0;canvas.height=0; }
|
||||
signal?.throwIfAborted();
|
||||
render=page.render({canvasContext:canvas.getContext('2d'),viewport});
|
||||
await awaitWithAbort(render.promise,signal,()=>render.cancel());
|
||||
signal?.throwIfAborted();
|
||||
return (await awaitWithAbort(worker.recognize(canvas),signal,stopWorker)).data.text;
|
||||
} finally { render=null;canvas.width=0;canvas.height=0; }
|
||||
} : undefined});
|
||||
return result;
|
||||
} finally {
|
||||
try { if(worker) await worker.terminate(); }
|
||||
signal?.removeEventListener('abort',cancel);
|
||||
try { await stopWorker(); }
|
||||
finally { await task.destroy(); }
|
||||
}
|
||||
}
|
||||
|
||||
export const extractPdfText=(file,options={})=>readPdf(file,{...options,ocr:false});
|
||||
export const ocrPdfText=(file,options={})=>readPdf(file,{...options,ocr:true,forceOcr:true});
|
||||
export async function extractPlainText(file) {
|
||||
return {text:(await file.text()).trim(),pageCount:1,usedPages:1,complete:true};
|
||||
export async function extractPlainText(file,{signal}={}) {
|
||||
signal?.throwIfAborted();
|
||||
return {text:(await awaitWithAbort(file.text(),signal)).trim(),pageCount:1,usedPages:1,complete:true};
|
||||
}
|
||||
export async function extractText(file,{ocr=true,onProgress}={}) {
|
||||
export async function extractText(file,{ocr=true,onProgress,signal}={}) {
|
||||
if((file.name || '').toLowerCase().endsWith('.pdf') || file.type==='application/pdf')
|
||||
return readPdf(file,{ocr,onProgress});
|
||||
return extractPlainText(file);
|
||||
return readPdf(file,{ocr,onProgress,signal});
|
||||
return extractPlainText(file,{signal});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user