@juspay/neurolink 12.22.0 → 12.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -6
- package/dist/browser/neurolink.min.js +372 -372
- package/dist/core/constants.js +1 -1
- package/dist/providers/openaiChatCompletionsClient.d.ts +4 -0
- package/dist/providers/openaiChatCompletionsClient.js +20 -5
- package/dist/types/file.d.ts +13 -0
- package/dist/utils/pdfProcessor.d.ts +21 -3
- package/dist/utils/pdfProcessor.js +124 -48
- package/docs-site/static/search-index.json +1 -1
- package/package.json +1 -3
package/dist/core/constants.js
CHANGED
|
@@ -311,7 +311,7 @@ export const PDF_LIMITS = {
|
|
|
311
311
|
DEFAULT_MAX_CANVAS_PIXELS: 16_777_216,
|
|
312
312
|
// Floor for the downscaled render scale (#260 follow-up). A crafted/malformed
|
|
313
313
|
// MediaBox can drive the estimated pixel count toward Infinity, which would
|
|
314
|
-
// otherwise collapse `effectiveScale` to 0 and hand
|
|
314
|
+
// otherwise collapse `effectiveScale` to 0 and hand the renderer a degenerate
|
|
315
315
|
// viewport. Never let the downscale branch go below this.
|
|
316
316
|
MIN_EFFECTIVE_SCALE: 0.1,
|
|
317
317
|
};
|
|
@@ -37,6 +37,10 @@ export declare const buildWireToolNameMaps: (names: readonly string[], reservedW
|
|
|
37
37
|
* tool definitions). Used by the per-request max_tokens fit against a
|
|
38
38
|
* RUNTIME-DISCOVERED context window — deliberately the same char-based
|
|
39
39
|
* estimator the budget pipeline uses, so both layers agree.
|
|
40
|
+
*
|
|
41
|
+
* Image parts are billed at the flat IMAGE_TOKEN_ESTIMATE, never by the length
|
|
42
|
+
* of their base64 data URL: a PDF rendered to 10 page images otherwise
|
|
43
|
+
* stringifies to ~1.2M "tokens" and is rejected before it is ever sent.
|
|
40
44
|
*/
|
|
41
45
|
export declare const estimateWireTokens: (messages: ReadonlyArray<OpenAICompatChatMessage>, tools: OpenAICompatChatTool[] | undefined, provider?: string) => number;
|
|
42
46
|
export declare const safeStringify: (value: unknown) => string;
|
|
@@ -17,7 +17,7 @@ import { createParser } from "eventsource-parser";
|
|
|
17
17
|
import { sanitizeToolName } from "../mcp/toolConverter.js";
|
|
18
18
|
import { resolveSamplingParams } from "../models/modelRegistry.js";
|
|
19
19
|
import { convertZodToJsonSchema, normalizeWireToolSchema, } from "../utils/schemaConversion.js";
|
|
20
|
-
import { estimateTokens, TOKENS_PER_MESSAGE, } from "../utils/tokenEstimation.js";
|
|
20
|
+
import { estimateTokens, IMAGE_TOKEN_ESTIMATE, TOKENS_PER_MESSAGE, } from "../utils/tokenEstimation.js";
|
|
21
21
|
export const stripTrailingSlash = (s) => s.replace(/\/+$/, "");
|
|
22
22
|
// OpenAI-compatible wire tool names: first char letter/underscore (some
|
|
23
23
|
// chat templates treat a leading digit or hyphen as invalid identifiers),
|
|
@@ -68,14 +68,29 @@ export const buildWireToolNameMaps = (names, reservedWireNames) => {
|
|
|
68
68
|
* tool definitions). Used by the per-request max_tokens fit against a
|
|
69
69
|
* RUNTIME-DISCOVERED context window — deliberately the same char-based
|
|
70
70
|
* estimator the budget pipeline uses, so both layers agree.
|
|
71
|
+
*
|
|
72
|
+
* Image parts are billed at the flat IMAGE_TOKEN_ESTIMATE, never by the length
|
|
73
|
+
* of their base64 data URL: a PDF rendered to 10 page images otherwise
|
|
74
|
+
* stringifies to ~1.2M "tokens" and is rejected before it is ever sent.
|
|
71
75
|
*/
|
|
72
76
|
export const estimateWireTokens = (messages, tools, provider) => {
|
|
73
77
|
let total = 0;
|
|
74
78
|
for (const message of messages) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
if (Array.isArray(message.content)) {
|
|
80
|
+
for (const part of message.content) {
|
|
81
|
+
total +=
|
|
82
|
+
part.type === "image_url"
|
|
83
|
+
? IMAGE_TOKEN_ESTIMATE
|
|
84
|
+
: estimateTokens(part.text, provider);
|
|
85
|
+
}
|
|
86
|
+
total += TOKENS_PER_MESSAGE;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
const content = typeof message.content === "string"
|
|
90
|
+
? message.content
|
|
91
|
+
: safeStringify(message.content);
|
|
92
|
+
total += estimateTokens(content, provider) + TOKENS_PER_MESSAGE;
|
|
93
|
+
}
|
|
79
94
|
// tool_calls only exists on the assistant variant of the message union.
|
|
80
95
|
const toolCalls = message.tool_calls;
|
|
81
96
|
if (toolCalls) {
|
package/dist/types/file.d.ts
CHANGED
|
@@ -552,6 +552,19 @@ export type PDFImagePage = {
|
|
|
552
552
|
/** Populated when this page failed to render (#294). */
|
|
553
553
|
error?: string;
|
|
554
554
|
};
|
|
555
|
+
/**
|
|
556
|
+
* A PDF opened for page rendering. Rendering goes through pdf-parse so the
|
|
557
|
+
* process loads exactly one pdfjs-dist copy — a second copy fails pdfjs's
|
|
558
|
+
* API-vs-Worker version check for whichever library loads second.
|
|
559
|
+
*/
|
|
560
|
+
export type PDFRenderDocument = {
|
|
561
|
+
/** Total pages in the document. */
|
|
562
|
+
length: number;
|
|
563
|
+
/** Render a 1-based page to a PNG buffer. */
|
|
564
|
+
getPage: (pageNumber: number) => Promise<Buffer>;
|
|
565
|
+
/** Release pdfjs resources held by the document. */
|
|
566
|
+
destroy: () => Promise<void>;
|
|
567
|
+
};
|
|
555
568
|
/**
|
|
556
569
|
* A single PDF queued for multimodal message building, normalised from either
|
|
557
570
|
* submission surface — `input.pdfFiles` or `input.content` with `type: "pdf"`
|
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
* - Native PDF support for providers that accept PDF directly (Google AI, Vertex, OpenAI, Anthropic, Bedrock)
|
|
6
6
|
* - PDF → Image conversion for providers that don't support native PDF (Azure, Mistral, Ollama)
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* Page counting, text extraction and page rendering all go through pdf-parse,
|
|
9
|
+
* so the process only ever loads pdf-parse's pdfjs-dist. pdfjs keeps its Node
|
|
10
|
+
* worker in a process-wide global, and a second pdfjs version fails its
|
|
11
|
+
* API-vs-Worker version check against whichever copy loaded first.
|
|
9
12
|
*/
|
|
10
13
|
import type { FileProcessingResult, PDFProcessorOptions, PDFProviderConfig, PDFImageConversionOptions, PDFImageConversionResult, PDFImagePage } from "../types/index.js";
|
|
11
14
|
export declare class PDFProcessor {
|
|
@@ -40,6 +43,21 @@ export declare class PDFProcessor {
|
|
|
40
43
|
* falls back to the regex estimate.
|
|
41
44
|
*/
|
|
42
45
|
private static getAccuratePageCount;
|
|
46
|
+
/**
|
|
47
|
+
* Font and CMap directories of the pdfjs-dist copy pdf-parse itself loads,
|
|
48
|
+
* so non-embedded standard fonts and CJK text render instead of dropping
|
|
49
|
+
* out. Resolved from pdf-parse's location, never our own, to stay on that
|
|
50
|
+
* single copy. Undefined when resolution fails; rendering still works, with
|
|
51
|
+
* pdfjs falling back to its built-in font substitution.
|
|
52
|
+
*/
|
|
53
|
+
private static pdfjsAssetDirs;
|
|
54
|
+
private static resolvePdfjsAssetDirs;
|
|
55
|
+
/**
|
|
56
|
+
* Open a PDF for page-by-page PNG rendering through pdf-parse. Rejects with
|
|
57
|
+
* pdf-parse's PasswordException for encrypted PDFs (#258), which the callers
|
|
58
|
+
* map to typed errors.
|
|
59
|
+
*/
|
|
60
|
+
private static openForRendering;
|
|
43
61
|
static estimateTokens(pageCount: number, mode?: "text-only" | "visual"): number;
|
|
44
62
|
/**
|
|
45
63
|
* Estimate the largest page's rendered pixel count from the PDF's MediaBox
|
|
@@ -96,7 +114,7 @@ export declare class PDFProcessor {
|
|
|
96
114
|
*
|
|
97
115
|
* NOT a wrapper of/over {@link convertToImages}, despite the similar
|
|
98
116
|
* contract — the two are independent, parallel implementations (each does
|
|
99
|
-
* its own
|
|
117
|
+
* its own document open, downscale calculation, and page loop)
|
|
100
118
|
* rather than one delegating to the other. Keep behavior changes (page
|
|
101
119
|
* isolation, downscale, password handling) in sync across both by hand.
|
|
102
120
|
*/
|
|
@@ -113,7 +131,7 @@ export declare class PDFProcessor {
|
|
|
113
131
|
* Check if PDF to image conversion is available
|
|
114
132
|
* Useful for feature detection
|
|
115
133
|
*
|
|
116
|
-
* @returns true if pdf-
|
|
134
|
+
* @returns true if the pdf-parse renderer is available
|
|
117
135
|
*/
|
|
118
136
|
static isImageConversionAvailable(): Promise<boolean>;
|
|
119
137
|
/**
|
|
@@ -5,8 +5,13 @@
|
|
|
5
5
|
* - Native PDF support for providers that accept PDF directly (Google AI, Vertex, OpenAI, Anthropic, Bedrock)
|
|
6
6
|
* - PDF → Image conversion for providers that don't support native PDF (Azure, Mistral, Ollama)
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* Page counting, text extraction and page rendering all go through pdf-parse,
|
|
9
|
+
* so the process only ever loads pdf-parse's pdfjs-dist. pdfjs keeps its Node
|
|
10
|
+
* worker in a process-wide global, and a second pdfjs version fails its
|
|
11
|
+
* API-vs-Worker version check against whichever copy loaded first.
|
|
9
12
|
*/
|
|
13
|
+
import { createRequire } from "node:module";
|
|
14
|
+
import path from "node:path";
|
|
10
15
|
import { PDF_LIMITS } from "../core/constants.js";
|
|
11
16
|
import { ErrorFactory } from "./errorHandling.js";
|
|
12
17
|
import { logger } from "./logger.js";
|
|
@@ -297,6 +302,76 @@ export class PDFProcessor {
|
|
|
297
302
|
return null;
|
|
298
303
|
}
|
|
299
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* Font and CMap directories of the pdfjs-dist copy pdf-parse itself loads,
|
|
307
|
+
* so non-embedded standard fonts and CJK text render instead of dropping
|
|
308
|
+
* out. Resolved from pdf-parse's location, never our own, to stay on that
|
|
309
|
+
* single copy. Undefined when resolution fails; rendering still works, with
|
|
310
|
+
* pdfjs falling back to its built-in font substitution.
|
|
311
|
+
*/
|
|
312
|
+
static pdfjsAssetDirs;
|
|
313
|
+
static resolvePdfjsAssetDirs() {
|
|
314
|
+
if (PDFProcessor.pdfjsAssetDirs === undefined) {
|
|
315
|
+
try {
|
|
316
|
+
const pdfParseEntry = createRequire(import.meta.url).resolve("pdf-parse");
|
|
317
|
+
const pdfjsRoot = path.dirname(createRequire(pdfParseEntry).resolve("pdfjs-dist/package.json"));
|
|
318
|
+
PDFProcessor.pdfjsAssetDirs = {
|
|
319
|
+
standardFontDataUrl: path.join(pdfjsRoot, "standard_fonts") + path.sep,
|
|
320
|
+
cMapUrl: path.join(pdfjsRoot, "cmaps") + path.sep,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
logger.debug(`[PDF→Image] pdfjs font/CMap directories unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
325
|
+
PDFProcessor.pdfjsAssetDirs = null;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return PDFProcessor.pdfjsAssetDirs ?? undefined;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Open a PDF for page-by-page PNG rendering through pdf-parse. Rejects with
|
|
332
|
+
* pdf-parse's PasswordException for encrypted PDFs (#258), which the callers
|
|
333
|
+
* map to typed errors.
|
|
334
|
+
*/
|
|
335
|
+
static async openForRendering(pdfBuffer, scale, password) {
|
|
336
|
+
const { PDFParse } = await import("pdf-parse");
|
|
337
|
+
const parser = new PDFParse({
|
|
338
|
+
data: new Uint8Array(pdfBuffer),
|
|
339
|
+
isEvalSupported: false,
|
|
340
|
+
cMapPacked: true,
|
|
341
|
+
...PDFProcessor.resolvePdfjsAssetDirs(),
|
|
342
|
+
...(password ? { password } : {}),
|
|
343
|
+
});
|
|
344
|
+
try {
|
|
345
|
+
const { total } = await parser.getInfo();
|
|
346
|
+
return {
|
|
347
|
+
length: total,
|
|
348
|
+
getPage: async (pageNumber) => {
|
|
349
|
+
const shot = await parser.getScreenshot({
|
|
350
|
+
partial: [pageNumber],
|
|
351
|
+
scale,
|
|
352
|
+
imageBuffer: true,
|
|
353
|
+
imageDataUrl: false,
|
|
354
|
+
});
|
|
355
|
+
const page = shot.pages[0];
|
|
356
|
+
if (!page || page.data.length === 0) {
|
|
357
|
+
throw new Error(`page ${pageNumber} rendered no image data`);
|
|
358
|
+
}
|
|
359
|
+
return Buffer.from(page.data);
|
|
360
|
+
},
|
|
361
|
+
destroy: async () => {
|
|
362
|
+
await parser.destroy().catch(() => {
|
|
363
|
+
/* cleanup - ignore destroy errors */
|
|
364
|
+
});
|
|
365
|
+
},
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
catch (error) {
|
|
369
|
+
await parser.destroy().catch(() => {
|
|
370
|
+
/* cleanup - ignore destroy errors */
|
|
371
|
+
});
|
|
372
|
+
throw error;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
300
375
|
static estimateTokens(pageCount, mode = "visual") {
|
|
301
376
|
if (mode === "text-only") {
|
|
302
377
|
return Math.ceil((pageCount / 3) * 1000);
|
|
@@ -395,16 +470,14 @@ export class PDFProcessor {
|
|
|
395
470
|
estimatedMemoryMB,
|
|
396
471
|
sizeMB: Number(sizeMB.toFixed(2)),
|
|
397
472
|
});
|
|
473
|
+
let document;
|
|
398
474
|
try {
|
|
399
|
-
// Dynamic import to avoid loading MuPDF binaries until needed
|
|
400
|
-
const pdfToImgModule = await import("pdf-to-img");
|
|
401
|
-
const pdf = pdfToImgModule.pdf;
|
|
402
475
|
logger.debug("[PDF→Image] Starting PDF to image conversion", {
|
|
403
476
|
bufferSize: pdfBuffer.length,
|
|
404
477
|
scale,
|
|
405
478
|
maxPages: maxPages || "all",
|
|
406
479
|
});
|
|
407
|
-
// #260: pre-flight page-size check WITHOUT rendering.
|
|
480
|
+
// #260: pre-flight page-size check WITHOUT rendering. The renderer applies
|
|
408
481
|
// `scale` uniformly with no per-page hook and no pixel guard, so a very
|
|
409
482
|
// large page (e.g. an architectural drawing with a huge MediaBox) can
|
|
410
483
|
// allocate gigabytes of canvas. Read the largest MediaBox from the PDF
|
|
@@ -416,7 +489,7 @@ export class PDFProcessor {
|
|
|
416
489
|
const downscale = Math.sqrt(maxCanvasPixels / largestPixels);
|
|
417
490
|
// Floor the result: an astronomically large (but now finite, see
|
|
418
491
|
// `largestPagePixels`) pixel estimate would otherwise push `downscale`
|
|
419
|
-
// — and therefore `effectiveScale` — toward 0, handing
|
|
492
|
+
// — and therefore `effectiveScale` — toward 0, handing the renderer a
|
|
420
493
|
// degenerate viewport instead of a small-but-renderable page.
|
|
421
494
|
effectiveScale = Math.max(PDF_LIMITS.MIN_EFFECTIVE_SCALE, scale * downscale);
|
|
422
495
|
// Recompute the ratio actually applied (may differ from `downscale`
|
|
@@ -431,14 +504,9 @@ export class PDFProcessor {
|
|
|
431
504
|
logger.warn(`[PDF→Image] ⚠️ ${msg}`);
|
|
432
505
|
warnings.push(msg);
|
|
433
506
|
}
|
|
434
|
-
//
|
|
435
|
-
//
|
|
436
|
-
|
|
437
|
-
// iterator — that lets one bad page be isolated instead of aborting all.
|
|
438
|
-
const document = await pdf(pdfBuffer, {
|
|
439
|
-
scale: effectiveScale,
|
|
440
|
-
...(password ? { password } : {}),
|
|
441
|
-
});
|
|
507
|
+
// Password forwarded for encrypted PDFs (#258). An indexed getPage()
|
|
508
|
+
// loop lets one bad page be isolated instead of aborting all.
|
|
509
|
+
document = await PDFProcessor.openForRendering(pdfBuffer, effectiveScale, password);
|
|
442
510
|
const totalPages = document.length;
|
|
443
511
|
// #294: convert page-by-page with per-page isolation. A single page whose
|
|
444
512
|
// canvas render throws (e.g. a degenerate MediaBox) no longer discards
|
|
@@ -518,6 +586,9 @@ export class PDFProcessor {
|
|
|
518
586
|
cause: error,
|
|
519
587
|
});
|
|
520
588
|
}
|
|
589
|
+
finally {
|
|
590
|
+
await document?.destroy();
|
|
591
|
+
}
|
|
521
592
|
}
|
|
522
593
|
/**
|
|
523
594
|
* Shared input validation for the image-conversion paths. Returns the PDF
|
|
@@ -568,7 +639,7 @@ export class PDFProcessor {
|
|
|
568
639
|
*
|
|
569
640
|
* NOT a wrapper of/over {@link convertToImages}, despite the similar
|
|
570
641
|
* contract — the two are independent, parallel implementations (each does
|
|
571
|
-
* its own
|
|
642
|
+
* its own document open, downscale calculation, and page loop)
|
|
572
643
|
* rather than one delegating to the other. Keep behavior changes (page
|
|
573
644
|
* isolation, downscale, password handling) in sync across both by hand.
|
|
574
645
|
*/
|
|
@@ -581,8 +652,6 @@ export class PDFProcessor {
|
|
|
581
652
|
maxCanvasPixels,
|
|
582
653
|
maxPages,
|
|
583
654
|
});
|
|
584
|
-
const pdfToImgModule = await import("pdf-to-img");
|
|
585
|
-
const pdf = pdfToImgModule.pdf;
|
|
586
655
|
// #260: uniform downscale so the largest page stays under maxCanvasPixels.
|
|
587
656
|
let effectiveScale = scale;
|
|
588
657
|
const largestPixels = PDFProcessor.largestPagePixels(pdfBuffer, scale);
|
|
@@ -596,10 +665,7 @@ export class PDFProcessor {
|
|
|
596
665
|
// being yielded as a page error.
|
|
597
666
|
let document;
|
|
598
667
|
try {
|
|
599
|
-
document = await
|
|
600
|
-
scale: effectiveScale,
|
|
601
|
-
...(password ? { password } : {}),
|
|
602
|
-
});
|
|
668
|
+
document = await PDFProcessor.openForRendering(pdfBuffer, effectiveScale, password);
|
|
603
669
|
}
|
|
604
670
|
catch (error) {
|
|
605
671
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -617,33 +683,43 @@ export class PDFProcessor {
|
|
|
617
683
|
}
|
|
618
684
|
const totalPages = document.length;
|
|
619
685
|
let converted = 0;
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
686
|
+
try {
|
|
687
|
+
for (let pageNum = 1; pageNum <= totalPages; pageNum++) {
|
|
688
|
+
if (maxPages !== undefined && pageNum - 1 >= maxPages) {
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
691
|
+
try {
|
|
692
|
+
const page = await document.getPage(pageNum);
|
|
693
|
+
const base64Image = page.toString("base64");
|
|
694
|
+
converted++;
|
|
695
|
+
if (onProgress) {
|
|
696
|
+
await onProgress({
|
|
697
|
+
pagesConverted: converted,
|
|
698
|
+
totalPages,
|
|
699
|
+
elapsedMs: Date.now() - startTime,
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
yield {
|
|
703
|
+
pageIndex: pageNum,
|
|
704
|
+
image: base64Image,
|
|
705
|
+
imageSizeBytes: page.length,
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
catch (pageError) {
|
|
709
|
+
const msg = pageError instanceof Error ? pageError.message : String(pageError);
|
|
710
|
+
logger.warn(`[PDF→Image] ⚠️ page ${pageNum} failed to render (stream): ${msg}`);
|
|
711
|
+
yield {
|
|
712
|
+
pageIndex: pageNum,
|
|
713
|
+
image: "",
|
|
714
|
+
imageSizeBytes: 0,
|
|
715
|
+
error: msg,
|
|
716
|
+
};
|
|
634
717
|
}
|
|
635
|
-
yield {
|
|
636
|
-
pageIndex: pageNum,
|
|
637
|
-
image: base64Image,
|
|
638
|
-
imageSizeBytes: page.length,
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
catch (pageError) {
|
|
642
|
-
const msg = pageError instanceof Error ? pageError.message : String(pageError);
|
|
643
|
-
logger.warn(`[PDF→Image] ⚠️ page ${pageNum} failed to render (stream): ${msg}`);
|
|
644
|
-
yield { pageIndex: pageNum, image: "", imageSizeBytes: 0, error: msg };
|
|
645
718
|
}
|
|
646
719
|
}
|
|
720
|
+
finally {
|
|
721
|
+
await document.destroy();
|
|
722
|
+
}
|
|
647
723
|
}
|
|
648
724
|
/**
|
|
649
725
|
* Convert a PDF file path to an array of base64 PNG images
|
|
@@ -661,11 +737,11 @@ export class PDFProcessor {
|
|
|
661
737
|
* Check if PDF to image conversion is available
|
|
662
738
|
* Useful for feature detection
|
|
663
739
|
*
|
|
664
|
-
* @returns true if pdf-
|
|
740
|
+
* @returns true if the pdf-parse renderer is available
|
|
665
741
|
*/
|
|
666
742
|
static async isImageConversionAvailable() {
|
|
667
743
|
try {
|
|
668
|
-
await import("pdf-
|
|
744
|
+
await import("pdf-parse");
|
|
669
745
|
return true;
|
|
670
746
|
}
|
|
671
747
|
catch {
|
|
@@ -683,7 +759,7 @@ export class PDFProcessor {
|
|
|
683
759
|
static estimateConversionMemoryUsage(pdfSizeBytes, pageCount, scale = PDF_LIMITS.DEFAULT_SCALE) {
|
|
684
760
|
// Rough estimation:
|
|
685
761
|
// - Each page at scale 2 produces ~1-3MB PNG
|
|
686
|
-
// -
|
|
762
|
+
// - pdfjs needs ~2x PDF size for processing
|
|
687
763
|
// - Output images need ~2MB per page on average
|
|
688
764
|
const pdfProcessingMB = (pdfSizeBytes / (1024 * 1024)) * 2;
|
|
689
765
|
const outputImagesMB = pageCount * 2 * scale;
|