openreader/src/lib/server/compute/local.ts
Richard R 4497f610c0 feat(pdf): implement granular PDF parse progress tracking and migrate to parseState
Introduce detailed progress reporting for PDF layout parsing, exposing phase and page-level updates to clients. Replace legacy parseStatus with a structured parseState field in the database schema, updating all relevant backend and API logic. Add SSE endpoint for real-time parse progress updates. Update client hooks and UI to reflect granular progress and improve user feedback during document parsing. Includes migration scripts and new parse-state utility module.
2026-05-21 10:27:16 -06:00

51 lines
1.9 KiB
TypeScript

import type { ComputeBackend, PdfLayoutInput, WhisperAlignInput, WhisperAlignResult } from '@/lib/server/compute/types';
import { getDocumentBlob } from '@/lib/server/documents/blobstore';
import { getTtsSegmentAudioObject } from '@/lib/server/tts/segments-blobstore';
import {
runPdfLayoutFromPdfBuffer,
runWhisperAlignmentFromAudioBuffer,
} from '@openreader/compute-core/local-runtime';
export class LocalComputeBackend implements ComputeBackend {
readonly mode = 'local' as const;
async alignWords(input: WhisperAlignInput): Promise<WhisperAlignResult> {
let audioBuffer = input.audioBuffer ?? null;
if (!audioBuffer && input.audioObjectKey) {
const bytes = new Uint8Array(await getTtsSegmentAudioObject(input.audioObjectKey));
audioBuffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
}
if (!audioBuffer) {
throw new Error('Local compute alignment requires audioBuffer or audioObjectKey');
}
return runWhisperAlignmentFromAudioBuffer({
audioBuffer,
text: input.text,
cacheKey: input.cacheKey,
lang: input.lang,
});
}
async parsePdfLayout(input: PdfLayoutInput) {
let pdfBytes = input.pdfBytes ?? null;
if (!pdfBytes && input.documentId && typeof input.namespace !== 'undefined') {
const bytes = new Uint8Array(await getDocumentBlob(input.documentId, input.namespace));
pdfBytes = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
}
if (!pdfBytes) {
throw new Error('Local compute PDF layout requires pdfBytes or (documentId + namespace)');
}
return {
parsed: (await runPdfLayoutFromPdfBuffer({
documentId: input.documentId,
pdfBytes,
onPageParsed: (page) => input.onProgress?.({
totalPages: page.totalPages,
pagesParsed: page.pageNumber,
currentPage: page.pageNumber,
phase: 'infer',
}),
})).parsed,
};
}
}