@stabrise/scaledp 0.1.0

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.
Files changed (60) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +218 -0
  3. package/dist/box-DAfzwfhA.d.ts +119 -0
  4. package/dist/config-g6IrKlDC.d.ts +80 -0
  5. package/dist/data-to-image-DoZ4jQ3R.js +54 -0
  6. package/dist/data-to-image-DoZ4jQ3R.js.map +1 -0
  7. package/dist/detect/index.d.ts +71 -0
  8. package/dist/detect/index.js +2 -0
  9. package/dist/detect-q8AI_Jdj.js +274 -0
  10. package/dist/detect-q8AI_Jdj.js.map +1 -0
  11. package/dist/detector-output-C0Qt-jEq.d.ts +13 -0
  12. package/dist/detector-output-lyF1Mqb8.js +13 -0
  13. package/dist/detector-output-lyF1Mqb8.js.map +1 -0
  14. package/dist/display/index.d.ts +66 -0
  15. package/dist/display/index.js +237 -0
  16. package/dist/display/index.js.map +1 -0
  17. package/dist/document-B8I61TiY.d.ts +16 -0
  18. package/dist/entity-CedtRhU1.d.ts +22 -0
  19. package/dist/entity-D6Hxaugj.js +13 -0
  20. package/dist/entity-D6Hxaugj.js.map +1 -0
  21. package/dist/image-CAH2rLv9.js +511 -0
  22. package/dist/image-CAH2rLv9.js.map +1 -0
  23. package/dist/image-Dc5TSg46.d.ts +18 -0
  24. package/dist/image-DoZDJkcR.js +37 -0
  25. package/dist/image-DoZDJkcR.js.map +1 -0
  26. package/dist/image-draw-boxes-De0QbFv9.js +285 -0
  27. package/dist/image-draw-boxes-De0QbFv9.js.map +1 -0
  28. package/dist/index.d.ts +269 -0
  29. package/dist/index.js +11 -0
  30. package/dist/model-cache-BEaqqRZ9.js +182 -0
  31. package/dist/model-cache-BEaqqRZ9.js.map +1 -0
  32. package/dist/model-cache-BhFYpfZz.d.ts +36 -0
  33. package/dist/ner/index.d.ts +293 -0
  34. package/dist/ner/index.js +2 -0
  35. package/dist/ner-SsZLZ6ed.js +1028 -0
  36. package/dist/ner-SsZLZ6ed.js.map +1 -0
  37. package/dist/ocr/index.d.ts +440 -0
  38. package/dist/ocr/index.js +3 -0
  39. package/dist/ocr-OHX2WM3e.js +1294 -0
  40. package/dist/ocr-OHX2WM3e.js.map +1 -0
  41. package/dist/ort-CXDoPrtw.js +73 -0
  42. package/dist/ort-CXDoPrtw.js.map +1 -0
  43. package/dist/params-DapwK9Ns.js +37 -0
  44. package/dist/params-DapwK9Ns.js.map +1 -0
  45. package/dist/pdf/index.d.ts +123 -0
  46. package/dist/pdf/index.js +2 -0
  47. package/dist/pdf-BQl0dneD.js +417 -0
  48. package/dist/pdf-BQl0dneD.js.map +1 -0
  49. package/dist/pipeline-DACqGkpN.js +240 -0
  50. package/dist/pipeline-DACqGkpN.js.map +1 -0
  51. package/dist/pipeline-DeLO-OCE.d.ts +139 -0
  52. package/dist/registry/index.d.ts +169 -0
  53. package/dist/registry/index.js +1061 -0
  54. package/dist/registry/index.js.map +1 -0
  55. package/dist/text-ahMLpxN9.js +109 -0
  56. package/dist/text-ahMLpxN9.js.map +1 -0
  57. package/dist/worker/index.d.ts +105 -0
  58. package/dist/worker/index.js +180 -0
  59. package/dist/worker/index.js.map +1 -0
  60. package/package.json +135 -0
@@ -0,0 +1,269 @@
1
+ import { _ as assertPositiveInt, a as PipelineOptions, c as RowTime, d as StageDescriptor, f as toRows, g as assertInRange, h as Validator, i as PipelineInput, l as Stage, m as BaseStageParams, n as ExecutionTime, o as ROW_TIME_COL, p as BASE_STAGE_DEFAULTS, r as Pipeline, s as Row, t as EXECUTION_TIME_COL, u as StageContext, v as resolveParams } from "./pipeline-DeLO-OCE.js";
2
+ import { S as polygonPerimeter, _ as RotatedRect, a as bbox, b as minAreaRect, c as boxIou, d as isRotated, f as mergeBoxes, g as Point, h as shape, i as ROTATION_EPSILON_DEGREES, l as createBox, m as scaleBox, n as Box, o as boxFromBBox, p as mergeOverlappingBoxes, r as BoxShape, s as boxFromPolygon, t as BBox, u as isOnSameLine, v as boxPoints, x as polygonArea, y as convexHull } from "./box-DAfzwfhA.js";
3
+ import { n as createDetectorOutput, t as DetectorOutput } from "./detector-output-C0Qt-jEq.js";
4
+ import { n as createDocument, r as mergeDocuments, t as Document } from "./document-B8I61TiY.js";
5
+ import { n as NerOutput, r as createNerOutput, t as Entity } from "./entity-CedtRhU1.js";
6
+ import { n as ScaleDpImage, r as createImage, t as ImageFormat } from "./image-Dc5TSg46.js";
7
+ import { a as getConfig, i as defaultNumThreads, n as ScaleDpConfig, o as resetConfig, r as configure, s as resolveNumThreads, t as ModelProgress } from "./config-g6IrKlDC.js";
8
+ import { a as ensureModelFiles, c as isCached, i as cacheKey, n as ModelFiles, o as evict, r as ModelSpec, s as fileUrl, t as ModelFile } from "./model-cache-BhFYpfZz.js";
9
+ //#region src/core/errors.d.ts
10
+ /**
11
+ * ScaleDP stages never throw by default: a failure is captured into the output
12
+ * schema's `exception` field so a pipeline always completes and partial results
13
+ * survive. `propagateError: true` opts a stage into throwing instead.
14
+ */
15
+ declare class ScaleDpError extends Error {
16
+ readonly stage: string;
17
+ readonly cause?: unknown | undefined;
18
+ constructor(message: string, stage: string, cause?: unknown | undefined);
19
+ }
20
+ declare class ImageError extends ScaleDpError {
21
+ readonly name = "ImageError";
22
+ }
23
+ declare class OcrError extends ScaleDpError {
24
+ readonly name = "OcrError";
25
+ }
26
+ declare class DetectionError extends ScaleDpError {
27
+ readonly name = "DetectionError";
28
+ }
29
+ declare class NerError extends ScaleDpError {
30
+ readonly name = "NerError";
31
+ }
32
+ declare class ConfigError extends ScaleDpError {
33
+ readonly name = "ConfigError";
34
+ }
35
+ /** Render a caught value the way Python writes a traceback into `exception`. */
36
+ declare function formatException(stage: string, error: unknown): string;
37
+ //#endregion
38
+ //#region src/core/image.d.ts
39
+ interface Size {
40
+ width: number;
41
+ height: number;
42
+ }
43
+ declare function createCanvas(width: number, height: number): OffscreenCanvas;
44
+ declare function context2d(canvas: OffscreenCanvas): OffscreenCanvasRenderingContext2D;
45
+ declare function decodeImage(data: Uint8Array | Blob): Promise<ImageBitmap>;
46
+ declare function toImageData(source: ImageBitmap | OffscreenCanvas): ImageData;
47
+ declare function imageDataToCanvas(image: ImageData): OffscreenCanvas;
48
+ declare function encodeImage(source: ImageData | OffscreenCanvas, type?: 'image/png' | 'image/webp' | 'image/jpeg', quality?: number): Promise<Uint8Array>;
49
+ /** Read the intrinsic size of encoded image bytes without keeping the bitmap. */
50
+ declare function probeImageSize(data: Uint8Array | Blob): Promise<Size>;
51
+ interface LetterboxResult {
52
+ canvas: OffscreenCanvas;
53
+ /** Uniform scale applied to the source. */
54
+ scale: number;
55
+ /** Size the source occupies inside the target canvas. */
56
+ resized: Size;
57
+ /** Original source size. */
58
+ source: Size;
59
+ }
60
+ /**
61
+ * Fit an image into `target` preserving aspect ratio.
62
+ *
63
+ * `padding: 'end'` pads bottom and right only, matching PaddleOCR's detection
64
+ * preprocessing -- coordinates then restore by dividing by `scale`, with no
65
+ * offset to subtract. `padding: 'center'` centres the image, matching the YOLO
66
+ * preprocessing, where the pad offsets must be subtracted before unscaling.
67
+ */
68
+ declare function letterbox(source: ImageBitmap | OffscreenCanvas, target: Size, opts?: {
69
+ padding?: 'end' | 'center';
70
+ fill?: string;
71
+ }): LetterboxResult;
72
+ /** Uniform resize by a scale factor. */
73
+ declare function resize(source: ImageBitmap | OffscreenCanvas, factor: number): OffscreenCanvas;
74
+ /**
75
+ * Crop a box out of an image, straightening it if it is rotated.
76
+ *
77
+ * Port of `TesseractRecognizer._prepare_box_for_ocr`. For upright boxes this is
78
+ * a plain crop; for rotated ones it applies the affine transform that maps the
79
+ * box's own corners onto the destination rectangle, which is what makes a
80
+ * recognizer see level text.
81
+ */
82
+ /**
83
+ * The transform `cropBox` applies, as a function from crop pixel to source
84
+ * pixel.
85
+ *
86
+ * Kept separate so anything that needs to map *back* -- word boxes recognised
87
+ * inside a crop, say -- uses the same geometry the crop was made with, rather
88
+ * than a second copy of it that can drift.
89
+ */
90
+ interface CropGeometry {
91
+ /** The box after scaling and padding, in source coordinates. */
92
+ scaled: Box;
93
+ width: number;
94
+ height: number;
95
+ /** Crop pixel -> source pixel. */
96
+ map: (x: number, y: number) => Point;
97
+ }
98
+ declare function cropGeometry(box: Box, opts?: {
99
+ scaleFactor?: number;
100
+ padding?: number;
101
+ }): CropGeometry;
102
+ declare function cropBox(source: ImageBitmap | OffscreenCanvas, box: Box, opts?: {
103
+ scaleFactor?: number;
104
+ padding?: number;
105
+ }): OffscreenCanvas;
106
+ /**
107
+ * Build an NCHW float32 tensor from an image.
108
+ *
109
+ * `bgr` exists because ScaleDP's DBNet path converts RGB->BGR and never swaps
110
+ * back, so the model is fed BGR channels against RGB ImageNet statistics.
111
+ * Replicating that quirk is required for box parity.
112
+ */
113
+ declare function toNchwFloat32(image: ImageData, opts?: {
114
+ mean?: readonly number[];
115
+ std?: readonly number[];
116
+ scale?: number;
117
+ bgr?: boolean;
118
+ }): Float32Array;
119
+ declare const IMAGENET_MEAN: readonly [0.485, 0.456, 0.406];
120
+ declare const IMAGENET_STD: readonly [0.229, 0.224, 0.225];
121
+ //#endregion
122
+ //#region src/core/text.d.ts
123
+ /**
124
+ * Arrange items into groups whose successive elements differ by no more than
125
+ * `maxGap`. Unlike Python's `cluster`, this does not sort the caller's array
126
+ * in place.
127
+ */
128
+ declare function cluster<T>(items: readonly T[], maxGap: number, key?: (item: T) => number): T[][];
129
+ /**
130
+ * A robust central value: drop the first and last quartile, then take the mode.
131
+ *
132
+ * Trimming the tails is what makes this survive OCR outliers -- a single giant
133
+ * heading would otherwise skew a plain mean. Always returns a truncated
134
+ * integer, matching Python's `int(...)` wrapper.
135
+ */
136
+ declare function getSize<T>(items: readonly T[], key?: (item: T) => number): number;
137
+ /**
138
+ * Median-ish width of a single character, used to size inter-word gaps.
139
+ * Takes grouped lines, matching Python's signature.
140
+ */
141
+ declare function getCharacterWidth(lines: readonly (readonly Box[])[]): number;
142
+ /**
143
+ * Group boxes into text lines, each sorted left to right.
144
+ *
145
+ * `lineTolerance` of 0 means "derive it": a third of the typical character
146
+ * height -- tight enough to keep adjacent lines apart, loose enough to tolerate
147
+ * baseline jitter.
148
+ */
149
+ declare function groupBoxesIntoLines(boxes: readonly Box[], lineTolerance?: number): Box[][];
150
+ /**
151
+ * Render grouped lines back to text, preserving horizontal spacing and blank
152
+ * lines.
153
+ *
154
+ * Each line is indented from x = 0, so the left margin is preserved rather than
155
+ * every line being flushed left. Vertical gaps wider than two character heights
156
+ * become blank lines. The running `y` starts at 0, which means the first line is
157
+ * measured against the top of the page -- that is Python's behaviour and it is
158
+ * what makes a top margin survive.
159
+ */
160
+ declare function linesToFormattedText(lines: readonly (readonly Box[])[], characterHeight: number): string;
161
+ /** Rebuild layout-preserving text straight from a flat box list. */
162
+ declare function boxesToFormattedText(boxes: readonly Box[], lineTolerance?: number): string;
163
+ /** Plain reading-order join, the default when `keepFormatting` is off. */
164
+ declare function boxesToText(boxes: readonly Box[], separator?: string): string;
165
+ //#endregion
166
+ //#region src/stages/data-to-image.d.ts
167
+ interface DataToImageParams extends BaseStageParams {
168
+ /** Encoding recorded on the output; the bytes are passed through unchanged. */
169
+ imageType: ImageFormat;
170
+ /** DPI to record when the row carries no `resolution` field. */
171
+ resolution: number;
172
+ }
173
+ declare const DATA_TO_IMAGE_DEFAULTS: DataToImageParams;
174
+ declare class DataToImage extends Stage<DataToImageParams> {
175
+ readonly name = "DataToImage";
176
+ constructor(options?: Partial<DataToImageParams>);
177
+ protected apply(input: unknown, row: Row): Promise<ScaleDpImage>;
178
+ protected onError(message: string, row: Row): ScaleDpImage;
179
+ }
180
+ /** Coerce any accepted binary representation into bytes. */
181
+ declare function toBytes(input: unknown): Uint8Array;
182
+ //#endregion
183
+ //#region src/stages/image-crop-boxes.d.ts
184
+ interface ImageCropBoxesParams extends BaseStageParams {
185
+ /** [imageColumn, boxColumn]. */
186
+ inputCols: string[];
187
+ imageType: ImageFormat;
188
+ /** Grow each box by this many pixels before cropping. */
189
+ padding: number;
190
+ /** Maximum crops per page; 0 means all of them. */
191
+ limit: number;
192
+ /** Rotate portrait crops a quarter turn, so text reads horizontally. */
193
+ autoRotate: boolean;
194
+ /** Emit the whole page when nothing was detected, instead of failing. */
195
+ returnEmpty: boolean;
196
+ /** Column to write the source box alongside each crop. */
197
+ boxCol: string;
198
+ }
199
+ declare const IMAGE_CROP_BOXES_DEFAULTS: ImageCropBoxesParams;
200
+ declare class ImageCropBoxes extends Stage<ImageCropBoxesParams> {
201
+ readonly name = "ImageCropBoxes";
202
+ constructor(options?: Partial<ImageCropBoxesParams>);
203
+ protected expand(_input: unknown, row: Row, ctx: StageContext): Promise<Row[]>;
204
+ protected apply(): Promise<never>;
205
+ protected onError(message: string, row: Row): ScaleDpImage;
206
+ }
207
+ //#endregion
208
+ //#region src/stages/image-draw-boxes.d.ts
209
+ interface ImageDrawBoxesParams extends BaseStageParams {
210
+ /**
211
+ * First entry is the image; the rest are box or entity sources. `inputCol`
212
+ * is inherited and unused here -- multi-input stages address their columns
213
+ * through this list, as they do in Python.
214
+ */
215
+ inputCols: string[];
216
+ imageType: ImageFormat;
217
+ /** Fill boxes as well as outlining them. */
218
+ filled: boolean;
219
+ /** Fixed colour for every box. Unset colours by group instead. */
220
+ color: string | null;
221
+ lineWidth: number;
222
+ textSize: number;
223
+ /**
224
+ * Box or entity fields to render as a label above each box, joined by ':'.
225
+ * e.g. ['entity_group'] or ['text', 'score'].
226
+ */
227
+ displayDataList: string[];
228
+ /** Grow each box by this many pixels before drawing. */
229
+ padding: number;
230
+ /** Only draw these entity groups; empty draws all. */
231
+ whiteList: string[];
232
+ /** Never draw these entity groups. */
233
+ blackList: string[];
234
+ }
235
+ declare const IMAGE_DRAW_BOXES_DEFAULTS: ImageDrawBoxesParams;
236
+ /**
237
+ * A stable colour per group name.
238
+ *
239
+ * Python picks a random colour per group, which changes on every run and makes
240
+ * two renders of the same document impossible to compare. Hashing the name
241
+ * instead keeps 'PERSON' the same colour everywhere, and the fixed saturation
242
+ * and lightness keep every colour legible on a white page.
243
+ */
244
+ declare function colorForGroup(name: string): string;
245
+ declare class ImageDrawBoxes extends Stage<ImageDrawBoxesParams> {
246
+ readonly name = "ImageDrawBoxes";
247
+ constructor(options?: Partial<ImageDrawBoxesParams>);
248
+ /** The base class drives `apply` off inputCol; this stage reads several. */
249
+ protected apply(_input: unknown, row: Row): Promise<ScaleDpImage>;
250
+ private drawBoxes;
251
+ private drawEntities;
252
+ private drawBox;
253
+ /**
254
+ * The label chip, anchored to the box's own top edge.
255
+ *
256
+ * Python places it at `box.x, box.y`, which is the corner of the *axis-
257
+ * aligned envelope*. For an upright box that is the top-left corner and the
258
+ * label sits where you expect; for a rotated one the envelope corner can be
259
+ * a long way off the box -- a vertical line's label lands beside its middle,
260
+ * detached from the box it names. Here the chip is drawn in the box's own
261
+ * frame instead, so it rides the top edge at the box's angle whatever that
262
+ * angle is. Upright boxes come out exactly where Python puts them.
263
+ */
264
+ private drawLabel;
265
+ protected onError(message: string, row: Row): ScaleDpImage;
266
+ }
267
+ //#endregion
268
+ export { BASE_STAGE_DEFAULTS, BBox, type BaseStageParams, Box, BoxShape, ConfigError, CropGeometry, DATA_TO_IMAGE_DEFAULTS, DataToImage, type DataToImageParams, DetectionError, DetectorOutput, Document, EXECUTION_TIME_COL, Entity, type ExecutionTime, IMAGENET_MEAN, IMAGENET_STD, IMAGE_CROP_BOXES_DEFAULTS, IMAGE_DRAW_BOXES_DEFAULTS, ImageCropBoxes, type ImageCropBoxesParams, ImageDrawBoxes, type ImageDrawBoxesParams, ImageError, ImageFormat, LetterboxResult, type ModelFile, type ModelFiles, type ModelProgress, type ModelSpec, NerError, NerOutput, OcrError, Pipeline, type PipelineInput, type PipelineOptions, Point, ROTATION_EPSILON_DEGREES, ROW_TIME_COL, RotatedRect, type Row, type RowTime, type ScaleDpConfig, ScaleDpError, ScaleDpImage, Size, Stage, type StageContext, type StageDescriptor, type Validator, assertInRange, assertPositiveInt, bbox, boxFromBBox, boxFromPolygon, boxIou, boxPoints, boxesToFormattedText, boxesToText, cacheKey, cluster, colorForGroup, configure, context2d, convexHull, createBox, createCanvas, createDetectorOutput, createDocument, createImage, createNerOutput, cropBox, cropGeometry, decodeImage, defaultNumThreads, encodeImage, ensureModelFiles, evict, fileUrl, formatException, getCharacterWidth, getConfig, getSize, groupBoxesIntoLines, imageDataToCanvas, isCached, isOnSameLine, isRotated, letterbox, linesToFormattedText, mergeBoxes, mergeDocuments, mergeOverlappingBoxes, minAreaRect, polygonArea, polygonPerimeter, probeImageSize, resetConfig, resize, resolveNumThreads, resolveParams, scaleBox, shape, toBytes, toImageData, toNchwFloat32, toRows };
269
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ import { _ as resolveNumThreads, a as toRows, c as ImageError, d as ScaleDpError, f as formatException, g as resetConfig, h as getConfig, i as Stage, l as NerError, m as defaultNumThreads, n as Pipeline, o as ConfigError, p as configure, r as ROW_TIME_COL, s as DetectionError, t as EXECUTION_TIME_COL, u as OcrError } from "./pipeline-DACqGkpN.js";
2
+ import { A as polygonArea, C as mergeBoxes, D as boxPoints, E as shape, O as convexHull, S as isRotated, T as scaleBox, _ as boxFromBBox, a as cropBox, b as createBox, c as encodeImage, d as probeImageSize, f as resize, g as bbox, h as ROTATION_EPSILON_DEGREES, i as createCanvas, j as polygonPerimeter, k as minAreaRect, l as imageDataToCanvas, m as toNchwFloat32, n as IMAGENET_STD, o as cropGeometry, p as toImageData, r as context2d, s as decodeImage, t as IMAGENET_MEAN, u as letterbox, v as boxFromPolygon, w as mergeOverlappingBoxes, x as isOnSameLine, y as boxIou } from "./image-CAH2rLv9.js";
3
+ import { a as isCached, i as fileUrl, n as ensureModelFiles, r as evict, t as cacheKey } from "./model-cache-BEaqqRZ9.js";
4
+ import { i as resolveParams, n as assertInRange, r as assertPositiveInt, t as BASE_STAGE_DEFAULTS } from "./params-DapwK9Ns.js";
5
+ import { a as getSize, i as getCharacterWidth, n as boxesToText, o as groupBoxesIntoLines, r as cluster, s as linesToFormattedText, t as boxesToFormattedText } from "./text-ahMLpxN9.js";
6
+ import { t as createDetectorOutput } from "./detector-output-lyF1Mqb8.js";
7
+ import { n as createDocument, r as mergeDocuments, t as createImage } from "./image-DoZDJkcR.js";
8
+ import { t as createNerOutput } from "./entity-D6Hxaugj.js";
9
+ import { n as DataToImage, r as toBytes, t as DATA_TO_IMAGE_DEFAULTS } from "./data-to-image-DoZ4jQ3R.js";
10
+ import { a as ImageCropBoxes, i as IMAGE_CROP_BOXES_DEFAULTS, n as ImageDrawBoxes, r as colorForGroup, t as IMAGE_DRAW_BOXES_DEFAULTS } from "./image-draw-boxes-De0QbFv9.js";
11
+ export { BASE_STAGE_DEFAULTS, ConfigError, DATA_TO_IMAGE_DEFAULTS, DataToImage, DetectionError, EXECUTION_TIME_COL, IMAGENET_MEAN, IMAGENET_STD, IMAGE_CROP_BOXES_DEFAULTS, IMAGE_DRAW_BOXES_DEFAULTS, ImageCropBoxes, ImageDrawBoxes, ImageError, NerError, OcrError, Pipeline, ROTATION_EPSILON_DEGREES, ROW_TIME_COL, ScaleDpError, Stage, assertInRange, assertPositiveInt, bbox, boxFromBBox, boxFromPolygon, boxIou, boxPoints, boxesToFormattedText, boxesToText, cacheKey, cluster, colorForGroup, configure, context2d, convexHull, createBox, createCanvas, createDetectorOutput, createDocument, createImage, createNerOutput, cropBox, cropGeometry, decodeImage, defaultNumThreads, encodeImage, ensureModelFiles, evict, fileUrl, formatException, getCharacterWidth, getConfig, getSize, groupBoxesIntoLines, imageDataToCanvas, isCached, isOnSameLine, isRotated, letterbox, linesToFormattedText, mergeBoxes, mergeDocuments, mergeOverlappingBoxes, minAreaRect, polygonArea, polygonPerimeter, probeImageSize, resetConfig, resize, resolveNumThreads, resolveParams, scaleBox, shape, toBytes, toImageData, toNchwFloat32, toRows };
@@ -0,0 +1,182 @@
1
+ import { h as getConfig } from "./pipeline-DACqGkpN.js";
2
+ //#region src/core/model-cache.ts
3
+ /**
4
+ * Model file fetching with IndexedDB persistence and streaming progress.
5
+ *
6
+ * The pdftools prototype implements this twice -- once for GLiNER and once for
7
+ * PaddleOCR -- differing only in where sizes come from and whether an auth
8
+ * token is attached. Both behaviours are folded in here: sizes come from a HEAD
9
+ * request (real percentages, rather than the hardcoded byte counts the GLiNER
10
+ * copy used) with a declared `approxBytes` as fallback.
11
+ */
12
+ const STORE = "files";
13
+ const DB_VERSION = 1;
14
+ function openDb(name) {
15
+ return new Promise((resolve, reject) => {
16
+ const request = indexedDB.open(name, DB_VERSION);
17
+ request.onupgradeneeded = () => {
18
+ const db = request.result;
19
+ if (!db.objectStoreNames.contains(STORE)) db.createObjectStore(STORE);
20
+ };
21
+ request.onsuccess = () => resolve(request.result);
22
+ request.onerror = () => reject(request.error);
23
+ });
24
+ }
25
+ /**
26
+ * Run one store operation.
27
+ *
28
+ * Writes resolve on transaction *commit*, not on request success -- a request
29
+ * succeeding only means it was queued, so resolving there would report a
30
+ * durable write that a later abort could still roll back.
31
+ */
32
+ async function withStore(mode, fn) {
33
+ const db = await openDb(getConfig().cacheDbName);
34
+ try {
35
+ return await new Promise((resolve, reject) => {
36
+ const tx = db.transaction(STORE, mode);
37
+ const request = fn(tx.objectStore(STORE));
38
+ request.onerror = () => reject(request.error);
39
+ if (mode === "readonly") request.onsuccess = () => resolve(request.result);
40
+ else {
41
+ tx.oncomplete = () => resolve(request.result);
42
+ tx.onabort = () => reject(tx.error);
43
+ tx.onerror = () => reject(tx.error);
44
+ }
45
+ });
46
+ } finally {
47
+ db.close();
48
+ }
49
+ }
50
+ const cacheAvailable = () => getConfig().cache === "indexeddb" && typeof indexedDB !== "undefined";
51
+ function cacheKey(spec, path) {
52
+ return `${spec.repo}@${spec.revision ?? "main"}/${path}`;
53
+ }
54
+ /**
55
+ * Resolve a repo-relative path to a URL under the configured model host.
56
+ *
57
+ * A path that is already absolute is returned untouched -- some catalogues
58
+ * (ppu-paddle-ocr's, for one) publish full URLs against their own host, and
59
+ * rewriting those against `modelHost` would point at files that do not exist.
60
+ */
61
+ function fileUrl(spec, path) {
62
+ if (/^https?:\/\//.test(path)) return path;
63
+ const { modelHost } = getConfig();
64
+ const host = modelHost.replace(/\/+$/, "");
65
+ if (!/^https?:\/\/(?:[^/]*\.)?huggingface\.co/.test(host)) return `${host}/${spec.repo}/${path}`;
66
+ return `${host}/${spec.repo}/resolve/${spec.revision ?? "main"}/${path}`;
67
+ }
68
+ async function authHeaders(repo) {
69
+ const auth = getConfig().auth;
70
+ if (!auth) return void 0;
71
+ const token = await auth(repo);
72
+ return token ? { Authorization: `Bearer ${token}` } : void 0;
73
+ }
74
+ /** Fetch a URL as an ArrayBuffer, reporting each chunk's byte length. */
75
+ async function fetchWithProgress(url, headers, onChunk, signal) {
76
+ const response = await fetch(url, {
77
+ headers,
78
+ signal
79
+ });
80
+ if (!response.ok) throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`);
81
+ if (!response.body) {
82
+ const buffer = await response.arrayBuffer();
83
+ onChunk(buffer.byteLength);
84
+ return buffer;
85
+ }
86
+ const reader = response.body.getReader();
87
+ const chunks = [];
88
+ let total = 0;
89
+ for (;;) {
90
+ const { done, value } = await reader.read();
91
+ if (done) break;
92
+ if (!value) continue;
93
+ chunks.push(value);
94
+ total += value.byteLength;
95
+ onChunk(value.byteLength);
96
+ }
97
+ const out = new Uint8Array(total);
98
+ let offset = 0;
99
+ for (const chunk of chunks) {
100
+ out.set(chunk, offset);
101
+ offset += chunk.byteLength;
102
+ }
103
+ return out.buffer;
104
+ }
105
+ /** Byte size from a HEAD request, falling back to the declared estimate. */
106
+ async function contentLength(url, headers, fallback) {
107
+ try {
108
+ const response = await fetch(url, {
109
+ method: "HEAD",
110
+ headers
111
+ });
112
+ return Number(response.headers.get("content-length") ?? 0) || fallback;
113
+ } catch {
114
+ return fallback;
115
+ }
116
+ }
117
+ async function isCached(spec) {
118
+ if (!cacheAvailable()) return false;
119
+ for (const file of spec.files) if (!await withStore("readonly", (s) => s.get(cacheKey(spec, file.path)))) return false;
120
+ return true;
121
+ }
122
+ async function evict(spec) {
123
+ if (!cacheAvailable()) return;
124
+ for (const file of spec.files) await withStore("readwrite", (s) => s.delete(cacheKey(spec, file.path)));
125
+ }
126
+ /**
127
+ * Ensure every file in `spec` is available, downloading and caching what is
128
+ * missing. Returns buffers keyed by repo-relative path.
129
+ *
130
+ * Progress is aggregate across the files still to fetch; already-cached files
131
+ * count as instantly complete.
132
+ */
133
+ async function ensureModelFiles(spec, signal) {
134
+ const { onProgress } = getConfig();
135
+ const usable = cacheAvailable();
136
+ const result = {};
137
+ const missing = [];
138
+ for (const file of spec.files) {
139
+ const hit = usable ? await withStore("readonly", (s) => s.get(cacheKey(spec, file.path))) : void 0;
140
+ if (hit) result[file.path] = hit;
141
+ else missing.push(file);
142
+ }
143
+ if (missing.length === 0) {
144
+ onProgress?.({
145
+ repo: spec.repo,
146
+ file: "",
147
+ loaded: 0,
148
+ total: 0,
149
+ phase: "ready"
150
+ });
151
+ return result;
152
+ }
153
+ const headers = await authHeaders(spec.repo);
154
+ const total = (await Promise.all(missing.map((file) => file.approxBytes ? Promise.resolve(file.approxBytes) : contentLength(fileUrl(spec, file.path), headers, 0)))).reduce((sum, size) => sum + size, 0);
155
+ let loaded = 0;
156
+ for (const file of missing) {
157
+ const buffer = await fetchWithProgress(fileUrl(spec, file.path), headers, (bytes) => {
158
+ loaded += bytes;
159
+ onProgress?.({
160
+ repo: spec.repo,
161
+ file: file.path,
162
+ loaded,
163
+ total,
164
+ phase: "downloading"
165
+ });
166
+ }, signal);
167
+ if (usable) await withStore("readwrite", (s) => s.put(buffer, cacheKey(spec, file.path)));
168
+ result[file.path] = buffer;
169
+ }
170
+ onProgress?.({
171
+ repo: spec.repo,
172
+ file: "",
173
+ loaded: total,
174
+ total,
175
+ phase: "initializing"
176
+ });
177
+ return result;
178
+ }
179
+ //#endregion
180
+ export { isCached as a, fileUrl as i, ensureModelFiles as n, evict as r, cacheKey as t };
181
+
182
+ //# sourceMappingURL=model-cache-BEaqqRZ9.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-cache-BEaqqRZ9.js","names":[],"sources":["../src/core/model-cache.ts"],"sourcesContent":["/**\n * Model file fetching with IndexedDB persistence and streaming progress.\n *\n * The pdftools prototype implements this twice -- once for GLiNER and once for\n * PaddleOCR -- differing only in where sizes come from and whether an auth\n * token is attached. Both behaviours are folded in here: sizes come from a HEAD\n * request (real percentages, rather than the hardcoded byte counts the GLiNER\n * copy used) with a declared `approxBytes` as fallback.\n */\n\nimport { getConfig, type ModelProgress } from './config.js'\n\nconst STORE = 'files'\nconst DB_VERSION = 1\n\nexport interface ModelFile {\n /** Repo-relative path, e.g. 'onnx/model_quantized.onnx'. */\n path: string\n /** Fallback size used for progress when the server sends no content-length. */\n approxBytes?: number\n}\n\nexport interface ModelSpec {\n /** Hugging Face repo id, e.g. 'onnx-community/gliner_multi_pii-v1'. */\n repo: string\n revision?: string\n files: ModelFile[]\n}\n\nexport type ModelFiles = Record<string, ArrayBuffer>\n\nfunction openDb(name: string): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n const request = indexedDB.open(name, DB_VERSION)\n request.onupgradeneeded = () => {\n const db = request.result\n if (!db.objectStoreNames.contains(STORE)) db.createObjectStore(STORE)\n }\n request.onsuccess = () => resolve(request.result)\n request.onerror = () => reject(request.error)\n })\n}\n\n/**\n * Run one store operation.\n *\n * Writes resolve on transaction *commit*, not on request success -- a request\n * succeeding only means it was queued, so resolving there would report a\n * durable write that a later abort could still roll back.\n */\nasync function withStore<T>(\n mode: IDBTransactionMode,\n fn: (store: IDBObjectStore) => IDBRequest<T>\n): Promise<T> {\n const db = await openDb(getConfig().cacheDbName)\n try {\n return await new Promise<T>((resolve, reject) => {\n const tx = db.transaction(STORE, mode)\n const request = fn(tx.objectStore(STORE))\n request.onerror = () => reject(request.error)\n if (mode === 'readonly') {\n request.onsuccess = () => resolve(request.result)\n } else {\n tx.oncomplete = () => resolve(request.result)\n tx.onabort = () => reject(tx.error)\n tx.onerror = () => reject(tx.error)\n }\n })\n } finally {\n db.close()\n }\n}\n\nconst cacheAvailable = (): boolean => getConfig().cache === 'indexeddb' && typeof indexedDB !== 'undefined'\n\nexport function cacheKey(spec: ModelSpec, path: string): string {\n return `${spec.repo}@${spec.revision ?? 'main'}/${path}`\n}\n\n/**\n * Resolve a repo-relative path to a URL under the configured model host.\n *\n * A path that is already absolute is returned untouched -- some catalogues\n * (ppu-paddle-ocr's, for one) publish full URLs against their own host, and\n * rewriting those against `modelHost` would point at files that do not exist.\n */\nexport function fileUrl(spec: ModelSpec, path: string): string {\n if (/^https?:\\/\\//.test(path)) return path\n\n const { modelHost } = getConfig()\n const host = modelHost.replace(/\\/+$/, '')\n // A bare directory host (self-hosting) has no HF resolve/<revision> segment.\n if (!/^https?:\\/\\/(?:[^/]*\\.)?huggingface\\.co/.test(host)) {\n return `${host}/${spec.repo}/${path}`\n }\n return `${host}/${spec.repo}/resolve/${spec.revision ?? 'main'}/${path}`\n}\n\nasync function authHeaders(repo: string): Promise<HeadersInit | undefined> {\n const auth = getConfig().auth\n if (!auth) return undefined\n const token = await auth(repo)\n return token ? { Authorization: `Bearer ${token}` } : undefined\n}\n\n/** Fetch a URL as an ArrayBuffer, reporting each chunk's byte length. */\nasync function fetchWithProgress(\n url: string,\n headers: HeadersInit | undefined,\n onChunk: (bytes: number) => void,\n signal?: AbortSignal\n): Promise<ArrayBuffer> {\n const response = await fetch(url, { headers, signal })\n if (!response.ok) {\n throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`)\n }\n if (!response.body) {\n const buffer = await response.arrayBuffer()\n onChunk(buffer.byteLength)\n return buffer\n }\n\n const reader = response.body.getReader()\n const chunks: Uint8Array[] = []\n let total = 0\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n if (!value) continue\n chunks.push(value)\n total += value.byteLength\n onChunk(value.byteLength)\n }\n\n const out = new Uint8Array(total)\n let offset = 0\n for (const chunk of chunks) {\n out.set(chunk, offset)\n offset += chunk.byteLength\n }\n return out.buffer\n}\n\n/** Byte size from a HEAD request, falling back to the declared estimate. */\nasync function contentLength(\n url: string,\n headers: HeadersInit | undefined,\n fallback: number\n): Promise<number> {\n try {\n const response = await fetch(url, { method: 'HEAD', headers })\n return Number(response.headers.get('content-length') ?? 0) || fallback\n } catch {\n return fallback\n }\n}\n\nexport async function isCached(spec: ModelSpec): Promise<boolean> {\n if (!cacheAvailable()) return false\n for (const file of spec.files) {\n const hit = await withStore('readonly', (s) => s.get(cacheKey(spec, file.path)))\n if (!hit) return false\n }\n return true\n}\n\nexport async function evict(spec: ModelSpec): Promise<void> {\n if (!cacheAvailable()) return\n for (const file of spec.files) {\n await withStore('readwrite', (s) => s.delete(cacheKey(spec, file.path)))\n }\n}\n\n/**\n * Ensure every file in `spec` is available, downloading and caching what is\n * missing. Returns buffers keyed by repo-relative path.\n *\n * Progress is aggregate across the files still to fetch; already-cached files\n * count as instantly complete.\n */\nexport async function ensureModelFiles(spec: ModelSpec, signal?: AbortSignal): Promise<ModelFiles> {\n const { onProgress } = getConfig()\n const usable = cacheAvailable()\n const result: ModelFiles = {}\n const missing: ModelFile[] = []\n\n for (const file of spec.files) {\n const hit = usable\n ? await withStore<ArrayBuffer | undefined>('readonly', (s) => s.get(cacheKey(spec, file.path)))\n : undefined\n if (hit) result[file.path] = hit\n else missing.push(file)\n }\n\n if (missing.length === 0) {\n onProgress?.({ repo: spec.repo, file: '', loaded: 0, total: 0, phase: 'ready' })\n return result\n }\n\n const headers = await authHeaders(spec.repo)\n\n // Size everything up front so progress is a real percentage rather than a\n // byte count climbing toward an unknown ceiling.\n //\n // A declared `approxBytes` is used as-is: a HEAD costs a full round-trip per\n // file (and on Hugging Face it follows a redirect to the CDN), which doubles\n // the request count for a download that is already the slow part. Only\n // files with no estimate are probed, and those probes run together.\n const sizes = await Promise.all(\n missing.map((file) =>\n file.approxBytes\n ? Promise.resolve(file.approxBytes)\n : contentLength(fileUrl(spec, file.path), headers, 0)\n )\n )\n const total = sizes.reduce((sum, size) => sum + size, 0)\n\n let loaded = 0\n for (const file of missing) {\n const buffer = await fetchWithProgress(\n fileUrl(spec, file.path),\n headers,\n (bytes) => {\n loaded += bytes\n onProgress?.({\n repo: spec.repo,\n file: file.path,\n loaded,\n total,\n phase: 'downloading',\n })\n },\n signal\n )\n if (usable) {\n await withStore('readwrite', (s) => s.put(buffer, cacheKey(spec, file.path)))\n }\n result[file.path] = buffer\n }\n\n onProgress?.({ repo: spec.repo, file: '', loaded: total, total, phase: 'initializing' })\n return result\n}\n\nexport type { ModelProgress }\n"],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ //#region src/core/model-cache.d.ts
2
+ interface ModelFile {
3
+ /** Repo-relative path, e.g. 'onnx/model_quantized.onnx'. */
4
+ path: string;
5
+ /** Fallback size used for progress when the server sends no content-length. */
6
+ approxBytes?: number;
7
+ }
8
+ interface ModelSpec {
9
+ /** Hugging Face repo id, e.g. 'onnx-community/gliner_multi_pii-v1'. */
10
+ repo: string;
11
+ revision?: string;
12
+ files: ModelFile[];
13
+ }
14
+ type ModelFiles = Record<string, ArrayBuffer>;
15
+ declare function cacheKey(spec: ModelSpec, path: string): string;
16
+ /**
17
+ * Resolve a repo-relative path to a URL under the configured model host.
18
+ *
19
+ * A path that is already absolute is returned untouched -- some catalogues
20
+ * (ppu-paddle-ocr's, for one) publish full URLs against their own host, and
21
+ * rewriting those against `modelHost` would point at files that do not exist.
22
+ */
23
+ declare function fileUrl(spec: ModelSpec, path: string): string;
24
+ declare function isCached(spec: ModelSpec): Promise<boolean>;
25
+ declare function evict(spec: ModelSpec): Promise<void>;
26
+ /**
27
+ * Ensure every file in `spec` is available, downloading and caching what is
28
+ * missing. Returns buffers keyed by repo-relative path.
29
+ *
30
+ * Progress is aggregate across the files still to fetch; already-cached files
31
+ * count as instantly complete.
32
+ */
33
+ declare function ensureModelFiles(spec: ModelSpec, signal?: AbortSignal): Promise<ModelFiles>;
34
+ //#endregion
35
+ export { ensureModelFiles as a, isCached as c, cacheKey as i, ModelFiles as n, evict as o, ModelSpec as r, fileUrl as s, ModelFile as t };
36
+ //# sourceMappingURL=model-cache-BhFYpfZz.d.ts.map