@stabrise/scaledp 0.1.0 → 0.1.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.
Files changed (30) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +3 -2
  3. package/dist/{data-to-image-DoZ4jQ3R.js → data-to-image-CRvh92wt.js} +2 -2
  4. package/dist/{data-to-image-DoZ4jQ3R.js.map → data-to-image-CRvh92wt.js.map} +1 -1
  5. package/dist/detect/index.js +1 -1
  6. package/dist/{detect-q8AI_Jdj.js → detect-DpTc5Wtc.js} +3 -3
  7. package/dist/{detect-q8AI_Jdj.js.map → detect-DpTc5Wtc.js.map} +1 -1
  8. package/dist/{image-CAH2rLv9.js → image-DRBsbv7G.js} +16 -2
  9. package/dist/{image-CAH2rLv9.js.map → image-DRBsbv7G.js.map} +1 -1
  10. package/dist/{image-draw-boxes-De0QbFv9.js → image-draw-boxes-DuR8eBeg.js} +2 -2
  11. package/dist/{image-draw-boxes-De0QbFv9.js.map → image-draw-boxes-DuR8eBeg.js.map} +1 -1
  12. package/dist/index.d.ts +8 -1
  13. package/dist/index.js +4 -4
  14. package/dist/ner/index.js +1 -1
  15. package/dist/{ner-SsZLZ6ed.js → ner-BB3vGNxO.js} +2 -2
  16. package/dist/{ner-SsZLZ6ed.js.map → ner-BB3vGNxO.js.map} +1 -1
  17. package/dist/ocr/index.d.ts +100 -4
  18. package/dist/ocr/index.js +3 -3
  19. package/dist/{ocr-OHX2WM3e.js → ocr-Dkkbk0Wl.js} +282 -25
  20. package/dist/ocr-Dkkbk0Wl.js.map +1 -0
  21. package/dist/{ort-CXDoPrtw.js → ort-DZEG14nY.js} +25 -4
  22. package/dist/ort-DZEG14nY.js.map +1 -0
  23. package/dist/pdf/index.js +1 -1
  24. package/dist/{pdf-BQl0dneD.js → pdf-qbdnOnqZ.js} +3 -3
  25. package/dist/{pdf-BQl0dneD.js.map → pdf-qbdnOnqZ.js.map} +1 -1
  26. package/dist/registry/index.js +97 -8
  27. package/dist/registry/index.js.map +1 -1
  28. package/package.json +2 -1
  29. package/dist/ocr-OHX2WM3e.js.map +0 -1
  30. package/dist/ort-CXDoPrtw.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
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";
2
+ import { A as minAreaRect, C as isRotated, D as shape, E as scaleBox, M as polygonPerimeter, O as boxPoints, S as isOnSameLine, T as mergeOverlappingBoxes, _ as bbox, a as cropBox, b as boxIou, c as encodeImage, d as probeImageSize, f as resize, g as ROTATION_EPSILON_DEGREES, h as toNchwFloat32, i as createCanvas, j as polygonArea, k as convexHull, l as imageDataToCanvas, m as toImageData, n as IMAGENET_STD, o as cropGeometry, p as rotate180, r as context2d, s as decodeImage, t as IMAGENET_MEAN, u as letterbox, v as boxFromBBox, w as mergeBoxes, x as createBox, y as boxFromPolygon } from "./image-DRBsbv7G.js";
3
3
  import { a as isCached, i as fileUrl, n as ensureModelFiles, r as evict, t as cacheKey } from "./model-cache-BEaqqRZ9.js";
4
4
  import { i as resolveParams, n as assertInRange, r as assertPositiveInt, t as BASE_STAGE_DEFAULTS } from "./params-DapwK9Ns.js";
5
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
6
  import { t as createDetectorOutput } from "./detector-output-lyF1Mqb8.js";
7
7
  import { n as createDocument, r as mergeDocuments, t as createImage } from "./image-DoZDJkcR.js";
8
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 };
9
+ import { n as DataToImage, r as toBytes, t as DATA_TO_IMAGE_DEFAULTS } from "./data-to-image-CRvh92wt.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-DuR8eBeg.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, rotate180, scaleBox, shape, toBytes, toImageData, toNchwFloat32, toRows };
package/dist/ner/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { A as isMostlyUppercase, C as toSpanTokenizer, D as DEFAULT_CHUNK_STRIDE, E as DEFAULT_CHUNK_LENGTH, M as rebaseSpan, N as titleCaseAllCapsWords, O as chunkText, S as decodeSpans, T as softmax, _ as generateSpans, a as loadTokenizer, b as WORD_PATTERN, c as DEFAULT_NER_MODEL_ID, d as NER_MODELS, f as getNerModel, g as decodeEntities, h as computeDotProductScores, i as buildCharToBoxMap, j as normaliseCasing, k as dedupeSpans, l as DEFAULT_PII_LABELS, m as Gliner2Backend, n as GlinerNer, o as resetTransformers, p as modelSizeBytes, r as boxesForRange, s as toCallableTokenizer, t as GLINER_NER_DEFAULTS, u as GLINER2_PII_LABELS, v as Gliner1Backend, w as sigmoid, x as splitWords, y as RICH_WORD_PATTERN } from "../ner-SsZLZ6ed.js";
1
+ import { A as isMostlyUppercase, C as toSpanTokenizer, D as DEFAULT_CHUNK_STRIDE, E as DEFAULT_CHUNK_LENGTH, M as rebaseSpan, N as titleCaseAllCapsWords, O as chunkText, S as decodeSpans, T as softmax, _ as generateSpans, a as loadTokenizer, b as WORD_PATTERN, c as DEFAULT_NER_MODEL_ID, d as NER_MODELS, f as getNerModel, g as decodeEntities, h as computeDotProductScores, i as buildCharToBoxMap, j as normaliseCasing, k as dedupeSpans, l as DEFAULT_PII_LABELS, m as Gliner2Backend, n as GlinerNer, o as resetTransformers, p as modelSizeBytes, r as boxesForRange, s as toCallableTokenizer, t as GLINER_NER_DEFAULTS, u as GLINER2_PII_LABELS, v as Gliner1Backend, w as sigmoid, x as splitWords, y as RICH_WORD_PATTERN } from "../ner-BB3vGNxO.js";
2
2
  export { DEFAULT_CHUNK_LENGTH, DEFAULT_CHUNK_STRIDE, DEFAULT_NER_MODEL_ID, DEFAULT_PII_LABELS, GLINER2_PII_LABELS, GLINER_NER_DEFAULTS, Gliner1Backend, Gliner2Backend, GlinerNer, NER_MODELS, RICH_WORD_PATTERN, WORD_PATTERN, boxesForRange, buildCharToBoxMap, chunkText, computeDotProductScores, decodeEntities, decodeSpans, dedupeSpans, generateSpans, getNerModel, isMostlyUppercase, loadTokenizer, modelSizeBytes, normaliseCasing, rebaseSpan, resetTransformers, sigmoid, softmax, splitWords, titleCaseAllCapsWords, toCallableTokenizer, toSpanTokenizer };
@@ -2,7 +2,7 @@ import { h as getConfig, i as Stage, l as NerError } from "./pipeline-DACqGkpN.j
2
2
  import { n as ensureModelFiles } from "./model-cache-BEaqqRZ9.js";
3
3
  import { i as resolveParams, t as BASE_STAGE_DEFAULTS } from "./params-DapwK9Ns.js";
4
4
  import { t as createNerOutput } from "./entity-D6Hxaugj.js";
5
- import { t as createSession } from "./ort-CXDoPrtw.js";
5
+ import { t as createSession } from "./ort-DZEG14nY.js";
6
6
  //#region src/ner/chunking.ts
7
7
  /** Python's `split_text` default, and what the cloud /ner/text endpoint uses. */
8
8
  const DEFAULT_CHUNK_LENGTH = 500;
@@ -1025,4 +1025,4 @@ var GlinerNer = class extends Stage {
1025
1025
  //#endregion
1026
1026
  export { isMostlyUppercase as A, toSpanTokenizer as C, DEFAULT_CHUNK_STRIDE as D, DEFAULT_CHUNK_LENGTH as E, rebaseSpan as M, titleCaseAllCapsWords as N, chunkText as O, decodeSpans as S, softmax as T, generateSpans as _, loadTokenizer as a, WORD_PATTERN as b, DEFAULT_NER_MODEL_ID as c, NER_MODELS as d, getNerModel as f, decodeEntities as g, computeDotProductScores as h, buildCharToBoxMap as i, normaliseCasing as j, dedupeSpans as k, DEFAULT_PII_LABELS as l, Gliner2Backend as m, GlinerNer as n, resetTransformers as o, modelSizeBytes as p, boxesForRange as r, toCallableTokenizer as s, GLINER_NER_DEFAULTS as t, GLINER2_PII_LABELS as u, Gliner1Backend as v, sigmoid as w, splitWords as x, RICH_WORD_PATTERN as y };
1027
1027
 
1028
- //# sourceMappingURL=ner-SsZLZ6ed.js.map
1028
+ //# sourceMappingURL=ner-BB3vGNxO.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ner-SsZLZ6ed.js","names":[],"sources":["../src/ner/chunking.ts","../src/ner/vendor/math.ts","../src/ner/tokenizer-types.ts","../src/ner/vendor/span-decoder.ts","../src/ner/vendor/splitter.ts","../src/ner/vendor/span-processor.ts","../src/ner/gliner1-backend.ts","../src/ner/vendor/gliner2-decoder.ts","../src/ner/gliner2-backend.ts","../src/ner/registry.ts","../src/ner/tokenizer.ts","../src/ner/gliner-ner.ts"],"sourcesContent":["/**\n * Long-text chunking and entity de-duplication for NER.\n *\n * Mirrors `Ner.split_text` in Python ScaleDP: fixed-length character windows\n * with a small overlap, offsets rebased onto the original text.\n *\n * Python has no cross-chunk de-duplication, so any entity landing in the\n * overlap is reported twice. That is fixed here.\n */\n\nimport type { DecodedSpan } from './vendor/span-decoder.js'\n\n/** Python's `split_text` default, and what the cloud /ner/text endpoint uses. */\nexport const DEFAULT_CHUNK_LENGTH = 500\n/** 500 - 480 leaves a 20-character overlap so entities on a seam survive. */\nexport const DEFAULT_CHUNK_STRIDE = 480\n\nexport interface Chunk {\n text: string\n /** Character offset of this chunk within the original text. */\n offset: number\n}\n\nexport function chunkText(\n text: string,\n maxLength = DEFAULT_CHUNK_LENGTH,\n stride = DEFAULT_CHUNK_STRIDE\n): Chunk[] {\n if (stride <= 0) throw new RangeError(`stride must be positive, received ${stride}`)\n if (maxLength <= 0) throw new RangeError(`maxLength must be positive, received ${maxLength}`)\n if (text.length === 0) return []\n if (text.length <= maxLength) return [{ text, offset: 0 }]\n\n const chunks: Chunk[] = []\n for (let offset = 0; offset < text.length; offset += stride) {\n const slice = text.slice(offset, offset + maxLength)\n chunks.push({ text: slice, offset })\n // A short slice means the end of the text; stop rather than emit\n // ever-shorter tails.\n if (slice.length < maxLength) break\n }\n return chunks\n}\n\n/** Shift a chunk-local span onto the original text's coordinates. */\nexport function rebaseSpan(span: DecodedSpan, offset: number): DecodedSpan {\n return { ...span, start: span.start + offset, end: span.end + offset }\n}\n\n/**\n * Drop duplicates produced by the chunk overlap, keeping the highest score for\n * each distinct (start, end, label).\n */\nexport function dedupeSpans(spans: readonly DecodedSpan[]): DecodedSpan[] {\n const best = new Map<string, DecodedSpan>()\n for (const span of spans) {\n const key = `${span.start}:${span.end}:${span.label}`\n const existing = best.get(key)\n if (!existing || span.score > existing.score) best.set(key, span)\n }\n return [...best.values()].sort((a, b) => a.start - b.start)\n}\n\n/** Ratio of uppercase to cased letters. */\nfunction uppercaseRatio(text: string): number {\n let upper = 0\n let lower = 0\n for (const char of text) {\n if (char >= 'A' && char <= 'Z') upper++\n else if (char >= 'a' && char <= 'z') lower++\n else if (char !== char.toLowerCase()) upper++\n else if (char !== char.toUpperCase()) lower++\n }\n return upper + lower === 0 ? 0 : upper / (upper + lower)\n}\n\nexport const ALL_CAPS_RATIO = 0.6\n\nexport function isMostlyUppercase(text: string): boolean {\n return uppercaseRatio(text) > ALL_CAPS_RATIO\n}\n\n/**\n * Title-case runs of capitals, preserving length.\n *\n * GLiNER1 models are cased and scanned documents are frequently set in all\n * caps, which reads to the model as unlike anything in training. Length\n * preservation is essential: every character offset the decoder returns is used\n * to index the original text.\n */\nexport function titleCaseAllCapsWords(text: string): string {\n return text.replace(/\\p{Lu}[\\p{Lu}\\p{N}'’-]*\\p{Lu}/gu, (word) => {\n const titled = (word[0] as string) + word.slice(1).toLowerCase()\n return titled.length === word.length ? titled : word\n })\n}\n\n/** Apply the casing fix only when the text is predominantly uppercase. */\nexport function normaliseCasing(text: string): string {\n return isMostlyUppercase(text) ? titleCaseAllCapsWords(text) : text\n}\n","/** Numeric helpers for the GLiNER runtimes. Adapted from @lmoe/gliner-onnx (MIT). */\n\n/** Numerically stable sigmoid: exp(-x) overflows for large negative x. */\nexport function sigmoid(x: number): number {\n if (x >= 0) return 1 / (1 + Math.exp(-x))\n const expX = Math.exp(x)\n return expX / (1 + expX)\n}\n\nexport function softmax(values: ArrayLike<number>): Float32Array {\n let max = Number.NEGATIVE_INFINITY\n for (let i = 0; i < values.length; i++) {\n const v = values[i] as number\n if (v > max) max = v\n }\n const out = new Float32Array(values.length)\n let sum = 0\n for (let i = 0; i < values.length; i++) {\n const e = Math.exp((values[i] as number) - max)\n out[i] = e\n sum += e\n }\n for (let i = 0; i < out.length; i++) out[i] = (out[i] as number) / sum\n return out\n}\n\n/** Gather rows out of a flat [n, hiddenSize] matrix. */\nexport function gatherRows(\n source: Float32Array,\n positions: readonly number[],\n hiddenSize: number\n): Float32Array {\n const out = new Float32Array(positions.length * hiddenSize)\n for (let i = 0; i < positions.length; i++) {\n const from = (positions[i] as number) * hiddenSize\n out.set(source.subarray(from, from + hiddenSize), i * hiddenSize)\n }\n return out\n}\n\n/** Contiguous row slice of a flat [n, hiddenSize] matrix. */\nexport function sliceRows(\n source: Float32Array,\n startRow: number,\n rowCount: number,\n hiddenSize: number\n): Float32Array {\n const from = startRow * hiddenSize\n return source.slice(from, from + rowCount * hiddenSize)\n}\n\n/** Token ids from a transformers.js `tolist()` result, flattened and de-bigint'd. */\nexport function extractTokenIds(tolistResult: (bigint | number)[][] | (bigint | number)[]): number[] {\n const flat = Array.isArray(tolistResult[0])\n ? (tolistResult as (bigint | number)[][]).flat()\n : (tolistResult as (bigint | number)[])\n return flat.map((v) => (typeof v === 'bigint' ? Number(v) : v))\n}\n","/** Shared tokenizer adaptation, kept separate so backends need not import the loader. */\n\nimport { NerError } from '../core/errors.js'\nimport { extractTokenIds } from './vendor/math.js'\nimport type { SpanTokenizer } from './vendor/span-processor.js'\n\nexport type PretrainedTokenizerLike = (\n text: string,\n options?: { add_special_tokens?: boolean }\n) => { input_ids: { tolist(): (bigint | number)[][] | (bigint | number)[] } }\n\n/**\n * Adapt a transformers.js tokenizer to the span processor's interface.\n *\n * CLS/SEP ids are derived empirically -- encode a throwaway token, read the\n * first and last id -- rather than read from `cls_token_id`. Not every GLiNER\n * repo populates those fields, and a wrong id corrupts every sequence silently\n * instead of failing loudly.\n */\nexport function toSpanTokenizer(tokenizer: PretrainedTokenizerLike): SpanTokenizer {\n const encode = (text: string): number[] =>\n extractTokenIds(tokenizer(text, { add_special_tokens: true }).input_ids.tolist())\n\n const probe = encode('x')\n if (probe.length < 2) {\n throw new NerError(\n 'Tokenizer produced no special tokens; cannot derive CLS/SEP ids',\n 'toSpanTokenizer'\n )\n }\n return { encode, clsTokenId: probe[0] as number, sepTokenId: probe[probe.length - 1] as number }\n}\n","/**\n * GLiNER1 span decoder. Adapted from @lmoe/gliner-onnx (MIT).\n *\n * The model emits logits shaped [batch, seqLen, maxWidth, entityCount]. Every\n * (start word, width, label) triple above threshold becomes a candidate span;\n * greedy non-maximum suppression then keeps the highest-scoring\n * non-overlapping set.\n */\n\nimport { sigmoid } from './math.js'\nimport type { SplitWord } from './splitter.js'\n\nexport interface DecodedSpan {\n text: string\n label: string\n start: number\n end: number\n score: number\n}\n\nexport interface DecodeOptions {\n threshold?: number\n /** Flat NER forbids nesting; set false to allow a span inside another. */\n flatNer?: boolean\n /** Allow the same span to carry more than one label. */\n multiLabel?: boolean\n}\n\n/** Model class ids are 1-based; index 0 is reserved. */\nconst ENTITY_ID_OFFSET = 1\n\nfunction spansOverlap(\n aStart: number,\n aEnd: number,\n bStart: number,\n bEnd: number,\n allowNested: boolean,\n allowMultiLabel: boolean\n): boolean {\n // Identical spans collide unless multi-label output is wanted.\n if (aStart === bStart && aEnd === bEnd) return !allowMultiLabel\n if (aStart > bEnd || bStart > aEnd) return false\n if (allowNested) {\n const nested = (aStart <= bStart && aEnd >= bEnd) || (bStart <= aStart && bEnd >= aEnd)\n if (nested) return false\n }\n return true\n}\n\n/** Keep the highest-scoring spans that do not collide. */\nfunction greedySearch(spans: DecodedSpan[], flatNer: boolean, multiLabel: boolean): DecodedSpan[] {\n const byScore = [...spans].sort((a, b) => b.score - a.score)\n const kept: DecodedSpan[] = []\n for (const span of byScore) {\n const collides = kept.some((other) =>\n spansOverlap(span.start, span.end, other.start, other.end, !flatNer, multiLabel)\n )\n if (!collides) kept.push(span)\n }\n return kept.sort((a, b) => a.start - b.start)\n}\n\nexport function decodeSpans(\n logits: ArrayLike<number>,\n params: {\n batchSize: number\n /** Words per sequence, i.e. the model's span-start axis. */\n inputLength: number\n maxWidth: number\n entityCount: number\n texts: readonly string[]\n batchWords: readonly SplitWord[][]\n idToClass: Record<number, string>\n },\n options: DecodeOptions = {}\n): DecodedSpan[][] {\n const threshold = options.threshold ?? 0.5\n const flatNer = options.flatNer ?? true\n const multiLabel = options.multiLabel ?? false\n\n const { batchSize, inputLength, maxWidth, entityCount } = params\n const batchStride = inputLength * maxWidth * entityCount\n const tokenStride = maxWidth * entityCount\n\n const spans: DecodedSpan[][] = Array.from({ length: batchSize }, () => [])\n\n for (let index = 0; index < logits.length; index++) {\n const score = sigmoid(logits[index] as number)\n if (score < threshold) continue\n\n const batchIdx = Math.floor(index / batchStride)\n const startWord = Math.floor(index / tokenStride) % inputLength\n const endWord = startWord + (Math.floor(index / entityCount) % maxWidth)\n const entityIdx = index % entityCount\n\n const words = params.batchWords[batchIdx]\n if (!words || startWord >= words.length || endWord >= words.length) continue\n\n const start = (words[startWord] as SplitWord)[1]\n const end = (words[endWord] as SplitWord)[2]\n const text = params.texts[batchIdx] ?? ''\n\n ;(spans[batchIdx] as DecodedSpan[]).push({\n text: text.slice(start, end),\n label: params.idToClass[entityIdx + ENTITY_ID_OFFSET] ?? '',\n start,\n end,\n score,\n })\n }\n\n return spans.map((batch) => greedySearch(batch, flatNer, multiLabel))\n}\n","/** Word splitting for GLiNER. Adapted from @lmoe/gliner-onnx (MIT). */\n\n/**\n * Unicode word classes (`\\p{L}\\p{N}` with the `u` flag), deliberately not `\\w`.\n *\n * JavaScript's `\\w` is ASCII-only even under `/u`, so it would shatter accented\n * names -- \"Müller\", \"García\" -- into single-character tokens and wreck\n * multi-word span detection in German, Polish and Spanish text. Python's `\\w`\n * is Unicode-aware, so this restores parity with the reference tokenizer.\n */\nexport const WORD_PATTERN = /[\\p{L}\\p{N}_]+(?:[-_][\\p{L}\\p{N}_]+)*|\\S/gu\n\n/**\n * As above, plus leading branches for URLs, emails and @mentions so they stay\n * whole. Those branches are ASCII on purpose, mirroring the Python pattern.\n */\nexport const RICH_WORD_PATTERN =\n /(?:https?:\\/\\/[^\\s]+|www\\.[^\\s]+)|[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}|@[a-z0-9_]+|[\\p{L}\\p{N}_]+(?:[-_][\\p{L}\\p{N}_]+)*|\\S/giu\n\nexport type SplitWord = [text: string, start: number, end: number]\n\n/**\n * Split text into words with their character offsets.\n *\n * A fresh RegExp per call: the `g` flag makes `lastIndex` stateful, so sharing\n * one instance across calls silently skips matches.\n */\nexport function splitWords(text: string, pattern: RegExp = WORD_PATTERN): SplitWord[] {\n const regex = new RegExp(pattern.source, pattern.flags)\n const out: SplitWord[] = []\n for (;;) {\n const match = regex.exec(text)\n if (match === null) break\n out.push([match[0], match.index, regex.lastIndex])\n }\n return out\n}\n","/**\n * GLiNER1 span-enumeration input builder. Adapted from @lmoe/gliner-onnx (MIT).\n *\n * Builds the prompt `<<ENT>> label1 <<ENT>> label2 <<SEP>> text` and enumerates\n * every candidate span up to `maxWidth` words, which is what the model scores.\n */\n\nimport { type SplitWord, splitWords } from './splitter.js'\n\nconst PAD = 0\n\nexport interface GlinerConfig {\n maxWidth: number\n entToken: string\n sepToken: string\n}\n\n/** Minimal tokenizer surface the processor needs. */\nexport interface SpanTokenizer {\n encode(text: string): number[]\n clsTokenId: number\n sepTokenId: number\n}\n\nexport interface ProcessorBatch {\n inputIds: number[][]\n attentionMasks: number[][]\n wordsMasks: number[][]\n textLengths: number[]\n spanIdxs: number[][][]\n spanMasks: boolean[][]\n /** 1-based entity index -> label, matching the model's class numbering. */\n idToClass: Record<number, string>\n batchWords: SplitWord[][]\n}\n\nfunction pad<T>(arrays: T[][], value: T): T[][] {\n const max = Math.max(...arrays.map((a) => a.length))\n return arrays.map((a) => [...a, ...new Array<T>(max - a.length).fill(value)])\n}\n\nexport class SpanProcessor {\n constructor(\n private readonly config: GlinerConfig,\n private readonly tokenizer: SpanTokenizer\n ) {}\n\n prepare(texts: readonly string[], labels: readonly string[]): ProcessorBatch {\n const batchWords = texts.map((text) => splitWords(text))\n\n const idToClass: Record<number, string> = {}\n for (const [i, label] of labels.entries()) idToClass[i + 1] = label\n\n const inputIds: number[][] = []\n const attentionMasks: number[][] = []\n const wordsMasks: number[][] = []\n const textLengths: number[] = []\n const spanIdxs: number[][][] = []\n const spanMasks: boolean[][] = []\n\n for (const words of batchWords) {\n textLengths.push(words.length)\n\n // Prompt tokens first, then the text's own words.\n const prompt: string[] = []\n for (const label of labels) prompt.push(this.config.entToken, label)\n prompt.push(this.config.sepToken)\n const sequence = [...prompt, ...words.map(([w]) => w)]\n\n const ids: number[] = [this.tokenizer.clsTokenId]\n const attention: number[] = [1]\n // wordsMask marks the FIRST sub-token of each text word with a\n // 1-based word index; prompt tokens and continuation sub-tokens get\n // 0. That is how the model pools sub-tokens back to words.\n const wordsMask: number[] = [PAD]\n let wordCounter = 1\n\n for (const [wordIdx, word] of sequence.entries()) {\n // slice(1, -1) drops the tokenizer's own CLS/SEP around a\n // single word.\n const subTokens = this.tokenizer.encode(word).slice(1, -1)\n for (const [tokenIdx, id] of subTokens.entries()) {\n ids.push(id)\n attention.push(1)\n if (wordIdx < prompt.length) wordsMask.push(PAD)\n else if (tokenIdx === 0) wordsMask.push(wordCounter++)\n else wordsMask.push(PAD)\n }\n }\n ids.push(this.tokenizer.sepTokenId)\n attention.push(1)\n wordsMask.push(PAD)\n\n inputIds.push(ids)\n attentionMasks.push(attention)\n wordsMasks.push(wordsMask)\n\n const spanIdx: number[][] = []\n const spanMask: boolean[] = []\n for (let start = 0; start < words.length; start++) {\n for (let width = 0; width < this.config.maxWidth; width++) {\n const end = Math.min(start + width, words.length - 1)\n spanIdx.push([start, end])\n spanMask.push(end < words.length)\n }\n }\n spanIdxs.push(spanIdx)\n spanMasks.push(spanMask)\n }\n\n const maxSpans = Math.max(...spanIdxs.map((s) => s.length))\n return {\n inputIds: pad(inputIds, PAD),\n attentionMasks: pad(attentionMasks, PAD),\n wordsMasks: pad(wordsMasks, PAD),\n textLengths,\n spanIdxs: spanIdxs.map((s) => [\n ...s,\n ...Array.from({ length: maxSpans - s.length }, () => [PAD, PAD]),\n ]),\n spanMasks: pad(spanMasks, false),\n idToClass,\n batchWords,\n }\n }\n}\n","/**\n * GLiNER1 span-enumeration runtime on onnxruntime-web.\n *\n * One ONNX graph. Inputs:\n * input_ids int64 [batch, tokens]\n * attention_mask int64 [batch, tokens]\n * words_mask int64 [batch, tokens] 1-based word index on first sub-token\n * text_lengths int64 [batch, 1]\n * span_idx int64 [batch, spans, 2]\n * span_mask bool [batch, spans]\n * Output:\n * logits float32 [batch, words, maxWidth, entityCount]\n */\n\nimport { NerError } from '../core/errors.js'\nimport type { ModelFiles } from '../core/model-cache.js'\nimport { createSession } from '../ocr/ort.js'\nimport type { NerBackend, NerBackendLoadOptions } from './backend.js'\nimport { type PretrainedTokenizerLike, toSpanTokenizer } from './tokenizer-types.js'\nimport { type DecodedSpan, decodeSpans } from './vendor/span-decoder.js'\nimport { type GlinerConfig, SpanProcessor } from './vendor/span-processor.js'\n\n/** Fallback when a repo's config omits max_width. */\nconst DEFAULT_MAX_WIDTH = 12\nconst DEFAULT_ENT_TOKEN = '<<ENT>>'\nconst DEFAULT_SEP_TOKEN = '<<SEP>>'\n\nconst CONFIG_CANDIDATES = ['gliner_config.json', 'config.json']\n/** Some repos put the graph at the root rather than under onnx/. */\nconst MODEL_EXTENSIONS = ['.onnx', '.model']\n\nconst OUTPUT_LOGITS = 'logits'\n\nfunction readConfig(files: ModelFiles): GlinerConfig {\n for (const name of CONFIG_CANDIDATES) {\n const raw = files[name]\n if (!raw) continue\n try {\n const parsed = JSON.parse(new TextDecoder().decode(raw)) as Record<string, unknown>\n return {\n maxWidth: Number(parsed.max_width) || DEFAULT_MAX_WIDTH,\n entToken: String(parsed.ent_token ?? DEFAULT_ENT_TOKEN),\n sepToken: String(parsed.sep_token ?? DEFAULT_SEP_TOKEN),\n }\n } catch {\n // Fall through to the next candidate rather than failing outright.\n }\n }\n return {\n maxWidth: DEFAULT_MAX_WIDTH,\n entToken: DEFAULT_ENT_TOKEN,\n sepToken: DEFAULT_SEP_TOKEN,\n }\n}\n\nfunction findModelBytes(files: ModelFiles): ArrayBuffer {\n for (const [path, bytes] of Object.entries(files)) {\n if (MODEL_EXTENSIONS.some((ext) => path.endsWith(ext))) return bytes\n }\n throw new NerError('No .onnx or .model file in the downloaded model', 'Gliner1Backend')\n}\n\nexport class Gliner1Backend implements NerBackend {\n readonly arch = 'gliner1' as const\n\n private session: import('onnxruntime-web').InferenceSession | null = null\n private processor: SpanProcessor | null = null\n private config: GlinerConfig | null = null\n\n async load(files: ModelFiles, options: NerBackendLoadOptions): Promise<void> {\n this.config = readConfig(files)\n this.processor = new SpanProcessor(\n this.config,\n toSpanTokenizer(options.tokenizer as PretrainedTokenizerLike)\n )\n this.session = await createSession(findModelBytes(files), {\n executionProviders: options.executionProviders,\n })\n }\n\n async extract(text: string, labels: readonly string[], threshold: number): Promise<DecodedSpan[]> {\n // Read directly rather than by destructuring: Biome's\n // noUnusedPrivateClassMembers does not see destructured member reads.\n const session = this.session\n const processor = this.processor\n const config = this.config\n if (!session || !processor || !config) {\n throw new NerError('Backend used before load()', 'Gliner1Backend')\n }\n if (labels.length === 0) return []\n\n const batch = processor.prepare([text], labels)\n const words = batch.batchWords[0] ?? []\n if (words.length === 0) return []\n\n const { Tensor } = await import('onnxruntime-web')\n const tokenCount = (batch.inputIds[0] as number[]).length\n const spanCount = (batch.spanIdxs[0] as number[][]).length\n\n const big = (values: number[]) => BigInt64Array.from(values, BigInt)\n\n const feeds: Record<string, import('onnxruntime-web').Tensor> = {\n input_ids: new Tensor('int64', big(batch.inputIds.flat()), [1, tokenCount]),\n attention_mask: new Tensor('int64', big(batch.attentionMasks.flat()), [1, tokenCount]),\n words_mask: new Tensor('int64', big(batch.wordsMasks.flat()), [1, tokenCount]),\n text_lengths: new Tensor('int64', big(batch.textLengths), [1, 1]),\n span_idx: new Tensor('int64', big(batch.spanIdxs.flat(2)), [1, spanCount, 2]),\n span_mask: new Tensor(\n 'bool',\n Uint8Array.from(batch.spanMasks.flat(), (v) => (v ? 1 : 0)),\n [1, spanCount]\n ),\n }\n\n // Only feed inputs this particular export actually declares: token-mode\n // GLiNER variants omit span_idx/span_mask entirely.\n const inputs: Record<string, import('onnxruntime-web').Tensor> = {}\n for (const name of session.inputNames) {\n const tensor = feeds[name]\n if (!tensor) throw new NerError(`Model expects unknown input \"${name}\"`, 'Gliner1Backend')\n inputs[name] = tensor\n }\n\n const outputs = await session.run(inputs)\n const logits = (outputs[OUTPUT_LOGITS] ?? outputs[session.outputNames[0] ?? ''])?.data\n if (!logits) throw new NerError('Model produced no logits', 'Gliner1Backend')\n\n const [spans = []] = decodeSpans(\n logits as Float32Array,\n {\n batchSize: 1,\n inputLength: words.length,\n maxWidth: config.maxWidth,\n entityCount: labels.length,\n texts: [text],\n batchWords: batch.batchWords,\n idToClass: batch.idToClass,\n },\n { threshold, flatNer: true, multiLabel: false }\n )\n return spans\n }\n\n async dispose(): Promise<void> {\n await this.session?.release()\n this.session = null\n this.processor = null\n this.config = null\n }\n}\n","/** GLiNER2 span generation and decoding. Adapted from @lmoe/gliner-onnx (MIT). */\n\nimport { sigmoid } from './math.js'\nimport type { DecodedSpan } from './span-decoder.js'\n\nexport type WordOffset = [start: number, end: number]\n\n/**\n * Every span up to `maxWidth` words.\n *\n * Out-of-range slots are filled with (0, 0) rather than dropped, because the\n * span axis must stay a fixed `seqLen * maxWidth` for the ONNX graph. The\n * decoder skips them by re-checking the word bounds.\n */\nexport function generateSpans(\n seqLen: number,\n maxWidth: number\n): { spanStart: number[]; spanEnd: number[]; spanCount: number } {\n const spanStart: number[] = []\n const spanEnd: number[] = []\n for (let i = 0; i < seqLen; i++) {\n for (let j = 0; j < maxWidth; j++) {\n const inRange = i + j < seqLen\n spanStart.push(inRange ? i : 0)\n spanEnd.push(inRange ? i + j : 0)\n }\n }\n return { spanStart, spanEnd, spanCount: spanStart.length }\n}\n\n/** Sigmoid of the dot product between each span and each label embedding. */\nexport function computeDotProductScores(\n spanRep: Float32Array,\n labelRep: Float32Array,\n spanCount: number,\n labelCount: number,\n hiddenSize: number\n): Float32Array {\n const scores = new Float32Array(spanCount * labelCount)\n for (let s = 0; s < spanCount; s++) {\n const spanOffset = s * hiddenSize\n for (let l = 0; l < labelCount; l++) {\n const labelOffset = l * hiddenSize\n let dot = 0\n for (let h = 0; h < hiddenSize; h++) {\n dot += (spanRep[spanOffset + h] as number) * (labelRep[labelOffset + h] as number)\n }\n scores[s * labelCount + l] = sigmoid(dot)\n }\n }\n return scores\n}\n\nexport interface NerScoreData {\n scores: Float32Array\n wordSpanStart: number[]\n wordSpanEnd: number[]\n spanCount: number\n}\n\n/** Score matrix -> entities, then drop same-label overlaps keeping the best. */\nexport function decodeEntities(\n scoreData: NerScoreData,\n wordCount: number,\n labels: readonly string[],\n wordOffsets: readonly WordOffset[],\n text: string,\n threshold: number\n): DecodedSpan[] {\n const { scores, wordSpanStart, wordSpanEnd, spanCount } = scoreData\n const labelCount = labels.length\n const entities: DecodedSpan[] = []\n\n for (let s = 0; s < spanCount; s++) {\n const startWord = wordSpanStart[s] as number\n const endWord = wordSpanEnd[s] as number\n if (startWord >= wordCount || endWord >= wordCount) continue\n\n for (let l = 0; l < labelCount; l++) {\n const score = scores[s * labelCount + l] as number\n if (score < threshold) continue\n\n const start = (wordOffsets[startWord] as WordOffset)[0]\n const end = (wordOffsets[endWord] as WordOffset)[1]\n entities.push({\n text: text.slice(start, end),\n label: labels[l] as string,\n start,\n end,\n score,\n })\n }\n }\n return deduplicateEntities(entities)\n}\n\n/**\n * Keep the highest-scoring entity among overlapping ones *of the same label*.\n * Different labels may overlap: a person name inside an organization is a\n * legitimate reading, not a conflict.\n */\nfunction deduplicateEntities(entities: readonly DecodedSpan[]): DecodedSpan[] {\n const byScore = [...entities].sort((a, b) => b.score - a.score)\n const kept: DecodedSpan[] = []\n for (const entity of byScore) {\n const overlaps = kept.some(\n (other) => entity.label === other.label && entity.start < other.end && entity.end > other.start\n )\n if (!overlaps) kept.push(entity)\n }\n return kept.sort((a, b) => a.start - b.start)\n}\n","/**\n * GLiNER2 runtime on onnxruntime-web.\n *\n * Three graphs, orchestrated in JS:\n * encoder input_ids, attention_mask [1, seq] int64 -> hidden states\n * span_rep hidden_states [1, seq, hidden] + span_start_idx/span_end_idx\n * [1, spans] int64 -> span representations\n * count_embed label_embeddings [labels, hidden] -> transformed embeddings\n *\n * The `classifier` graph is for text classification only and is not downloaded.\n * Scoring (dot product + sigmoid) happens in JS between span_rep and\n * count_embed.\n *\n * Unlike GLiNER1's single prompt string, GLiNER2 builds a *schema*:\n * ( [P] entities ( [E] label1 [E] label2 ) ) [SEP_TEXT] <text>\n * with each label's position recorded so its embedding can be gathered from\n * the encoder output.\n */\n\nimport { NerError } from '../core/errors.js'\nimport type { ModelFiles } from '../core/model-cache.js'\nimport { createSession } from '../ocr/ort.js'\nimport type { NerBackend, NerBackendLoadOptions } from './backend.js'\nimport type { PretrainedTokenizerLike } from './tokenizer-types.js'\nimport {\n computeDotProductScores,\n decodeEntities,\n generateSpans,\n type WordOffset,\n} from './vendor/gliner2-decoder.js'\nimport { extractTokenIds, gatherRows, sliceRows } from './vendor/math.js'\nimport type { DecodedSpan } from './vendor/span-decoder.js'\nimport { RICH_WORD_PATTERN, splitWords } from './vendor/splitter.js'\n\nconst CONFIG_FILE = 'config.json'\nconst GLINER2_CONFIG_FILE = 'gliner2_config.json'\n\nconst TOKEN_P = '[P]'\nconst TOKEN_E = '[E]'\nconst TOKEN_SEP_TEXT = '[SEP_TEXT]'\nconst SCHEMA_OPEN = '('\nconst SCHEMA_CLOSE = ')'\nconst NER_TASK_NAME = 'entities'\n\ninterface Gliner2Config {\n hiddenSize: number\n maxWidth: number\n specialTokens: Record<string, number>\n}\n\nfunction decodeJson<T>(buffer: ArrayBuffer | undefined, name: string): T {\n if (!buffer) throw new NerError(`Model is missing ${name}`, 'Gliner2Backend')\n return JSON.parse(new TextDecoder().decode(buffer)) as T\n}\n\nexport class Gliner2Backend implements NerBackend {\n readonly arch = 'gliner2' as const\n\n private config: Gliner2Config | null = null\n private encoder: import('onnxruntime-web').InferenceSession | null = null\n private spanRep: import('onnxruntime-web').InferenceSession | null = null\n private countEmbed: import('onnxruntime-web').InferenceSession | null = null\n private tokenize: ((text: string) => number[]) | null = null\n\n async load(files: ModelFiles, options: NerBackendLoadOptions): Promise<void> {\n const transformer = decodeJson<{ hidden_size: number }>(files[CONFIG_FILE], CONFIG_FILE)\n const gliner2 = decodeJson<{ max_width: number; special_tokens: Record<string, number> }>(\n files[GLINER2_CONFIG_FILE],\n GLINER2_CONFIG_FILE\n )\n if (typeof transformer.hidden_size !== 'number') {\n throw new NerError(`${CONFIG_FILE} is missing hidden_size`, 'Gliner2Backend')\n }\n if (typeof gliner2.max_width !== 'number') {\n throw new NerError(`${GLINER2_CONFIG_FILE} is missing max_width`, 'Gliner2Backend')\n }\n\n this.config = {\n hiddenSize: transformer.hidden_size,\n maxWidth: gliner2.max_width,\n specialTokens: gliner2.special_tokens,\n }\n\n const tokenizer = options.tokenizer as PretrainedTokenizerLike\n this.tokenize = (text: string) =>\n extractTokenIds(tokenizer(text, { add_special_tokens: false }).input_ids.tolist())\n\n const graph = (path: string): ArrayBuffer => {\n const bytes = files[path]\n if (!bytes) throw new NerError(`Model is missing ${path}`, 'Gliner2Backend')\n return bytes\n }\n\n // Pinned to WASM by the registry: ORT's WebGPU backend silently drops\n // entities here, because the dynamic span-gather and count_embed ops\n // fall back to CPU mid-graph and the partition boundary corrupts data.\n const providers = options.executionProviders ?? ['wasm']\n const [encoder, spanRep, countEmbed] = await Promise.all([\n createSession(graph('onnx/encoder.onnx'), { executionProviders: providers }),\n createSession(graph('onnx/span_rep.onnx'), { executionProviders: providers }),\n createSession(graph('onnx/count_embed.onnx'), { executionProviders: providers }),\n ])\n this.encoder = encoder\n this.spanRep = spanRep\n this.countEmbed = countEmbed\n }\n\n async extract(text: string, labels: readonly string[], threshold: number): Promise<DecodedSpan[]> {\n const { config, tokenize } = this\n if (!config || !tokenize || !this.encoder || !this.spanRep || !this.countEmbed) {\n throw new NerError('Backend used before load()', 'Gliner2Backend')\n }\n if (text.trim().length === 0 || labels.length === 0) return []\n\n const schema = this.buildSchema(labels)\n const words = this.tokenizeWords(text)\n if (words.wordOffsets.length === 0) return []\n\n const allTokens = [...schema.tokens, ...words.tokens]\n const hidden = await this.encode(allTokens)\n\n const labelEmbeddings = gatherRows(hidden, schema.labelPositions, config.hiddenSize)\n const textTokenCount = allTokens.length - schema.tokens.length\n if (textTokenCount === 0) return []\n\n const textHidden = sliceRows(hidden, schema.tokens.length, textTokenCount, config.hiddenSize)\n\n const { spanStart, spanEnd, spanCount } = generateSpans(words.wordOffsets.length, config.maxWidth)\n // Spans are enumerated over words but scored over tokens, so map each\n // word index to the position of its first sub-token.\n const toToken = (wordIdx: number) => words.firstTokenPositions[wordIdx] ?? 0\n const spanRepresentations = await this.runSpanRep(\n textHidden,\n textTokenCount,\n spanStart.map(toToken),\n spanEnd.map(toToken),\n spanCount\n )\n const transformedLabels = await this.runCountEmbed(labelEmbeddings, labels.length)\n\n const scores = computeDotProductScores(\n spanRepresentations,\n transformedLabels,\n spanCount,\n labels.length,\n config.hiddenSize\n )\n\n return decodeEntities(\n { scores, wordSpanStart: spanStart, wordSpanEnd: spanEnd, spanCount },\n words.wordOffsets.length,\n labels,\n words.wordOffsets,\n text,\n threshold\n )\n }\n\n /** `( [P] entities ( [E] label1 [E] label2 ) ) [SEP_TEXT]` */\n private buildSchema(labels: readonly string[]): { tokens: number[]; labelPositions: number[] } {\n const config = this.config as Gliner2Config\n const tokenize = this.tokenize as (text: string) => number[]\n\n const open = tokenize(SCHEMA_OPEN)\n const close = tokenize(SCHEMA_CLOSE)\n const tokens: number[] = [...open, config.specialTokens[TOKEN_P] as number]\n tokens.push(...tokenize(NER_TASK_NAME), ...open)\n\n const labelPositions: number[] = []\n for (const label of labels) {\n // Record the position of the [E] marker: that token's hidden state\n // is the label's embedding.\n labelPositions.push(tokens.length)\n tokens.push(config.specialTokens[TOKEN_E] as number, ...tokenize(label))\n }\n tokens.push(...close, ...close, config.specialTokens[TOKEN_SEP_TEXT] as number)\n return { tokens, labelPositions }\n }\n\n /**\n * Word-split and tokenize the text.\n *\n * Lower-cased before splitting, matching the reference implementation --\n * offsets stay valid because `toLowerCase` is length-preserving for the\n * scripts these models cover.\n */\n private tokenizeWords(text: string): {\n tokens: number[]\n wordOffsets: WordOffset[]\n firstTokenPositions: number[]\n } {\n const tokenize = this.tokenize as (text: string) => number[]\n const tokens: number[] = []\n const wordOffsets: WordOffset[] = []\n const firstTokenPositions: number[] = []\n\n for (const [word, start, end] of splitWords(text.toLowerCase(), RICH_WORD_PATTERN)) {\n wordOffsets.push([start, end])\n firstTokenPositions.push(tokens.length)\n tokens.push(...tokenize(word))\n }\n return { tokens, wordOffsets, firstTokenPositions }\n }\n\n private async encode(tokens: number[]): Promise<Float32Array> {\n const { Tensor } = await import('onnxruntime-web')\n const session = this.encoder as import('onnxruntime-web').InferenceSession\n const seqLen = tokens.length\n\n const outputs = await session.run({\n input_ids: new Tensor('int64', BigInt64Array.from(tokens, BigInt), [1, seqLen]),\n attention_mask: new Tensor('int64', new BigInt64Array(seqLen).fill(1n), [1, seqLen]),\n })\n return firstOutput(outputs, 'encoder')\n }\n\n private async runSpanRep(\n hidden: Float32Array,\n seqLen: number,\n spanStart: number[],\n spanEnd: number[],\n spanCount: number\n ): Promise<Float32Array> {\n const { Tensor } = await import('onnxruntime-web')\n const session = this.spanRep as import('onnxruntime-web').InferenceSession\n const hiddenSize = (this.config as Gliner2Config).hiddenSize\n\n const outputs = await session.run({\n hidden_states: new Tensor('float32', hidden, [1, seqLen, hiddenSize]),\n span_start_idx: new Tensor('int64', BigInt64Array.from(spanStart, BigInt), [1, spanCount]),\n span_end_idx: new Tensor('int64', BigInt64Array.from(spanEnd, BigInt), [1, spanCount]),\n })\n return firstOutput(outputs, 'span_rep')\n }\n\n private async runCountEmbed(labelEmbeddings: Float32Array, labelCount: number): Promise<Float32Array> {\n const { Tensor } = await import('onnxruntime-web')\n const session = this.countEmbed as import('onnxruntime-web').InferenceSession\n const hiddenSize = (this.config as Gliner2Config).hiddenSize\n\n const outputs = await session.run({\n label_embeddings: new Tensor('float32', labelEmbeddings, [labelCount, hiddenSize]),\n })\n return firstOutput(outputs, 'count_embed')\n }\n\n async dispose(): Promise<void> {\n await Promise.all([this.encoder?.release(), this.spanRep?.release(), this.countEmbed?.release()])\n this.encoder = null\n this.spanRep = null\n this.countEmbed = null\n this.config = null\n this.tokenize = null\n }\n}\n\nfunction firstOutput(\n outputs: import('onnxruntime-web').InferenceSession.OnnxValueMapType,\n graph: string\n): Float32Array {\n const [name] = Object.keys(outputs)\n if (!name) throw new NerError(`Graph ${graph} produced no output`, 'Gliner2Backend')\n return (outputs[name] as { data: Float32Array }).data\n}\n","/**\n * Registry of NER models that can run locally in the browser.\n *\n * Adding a model is a single entry here; the runtimes dispatch on `arch`.\n *\n * The default is deliberately a *public* model, so `npm i` works with no\n * configuration. StabRise's own PII models are private and stay opt-in: pass\n * their id and supply `configure({ auth })` to provide a token.\n */\n\nimport type { ModelFile } from '../core/model-cache.js'\n\nexport type NerArchitecture = 'gliner1' | 'gliner2'\n\nexport interface NerModel {\n /** Short id callers pass to GlinerNer. */\n id: string\n /** Human-readable name including the download size. */\n name: string\n arch: NerArchitecture\n /** Hugging Face repo id. */\n repo: string\n files: ModelFile[]\n /** Labels this model was tuned for. Using others still works, less well. */\n labels: readonly string[]\n languages: readonly string[]\n /** Private repos need `configure({ auth })` to supply a bearer token. */\n private?: boolean\n /** Execution providers this model requires; overrides the global config. */\n executionProviders?: readonly string[]\n}\n\n/** Generic PII labels, matching the pdftools prototype's default set. */\nexport const DEFAULT_PII_LABELS: readonly string[] = Object.freeze([\n 'person',\n 'organization',\n 'location',\n 'email',\n 'phone_number',\n 'url',\n 'id',\n 'account_number',\n 'zip_code',\n 'address',\n 'ip_address',\n 'date',\n 'ssn',\n 'driver_license',\n 'passport',\n 'age',\n 'credit_card',\n 'medical_condition',\n])\n\n/**\n * Label prompts the StabRise GLiNER2 PII model was fine-tuned on.\n *\n * These must match the cloud endpoint's tag list (scaledp-api\n * deidentify/views.py) or scores drop: GLiNER scores a label by its prompt\n * text, so a renamed label is a different label.\n */\nexport const GLINER2_PII_LABELS: readonly string[] = Object.freeze([\n 'date',\n 'person_name',\n 'person_title',\n 'organization',\n 'location',\n 'email',\n 'phone',\n 'id',\n 'account',\n 'zip_code',\n 'address',\n 'ip',\n 'url',\n 'ssn',\n 'driver_license',\n 'passport',\n 'age',\n 'credit_card',\n 'medical_condition',\n 'technology',\n])\n\nexport const NER_MODELS: readonly NerModel[] = Object.freeze([\n {\n id: 'gliner-multi-pii',\n name: 'GLiNER multilingual PII, int8 (~333 MB)',\n arch: 'gliner1',\n repo: 'onnx-community/gliner_multi_pii-v1',\n files: [\n { path: 'gliner_config.json', approxBytes: 800 },\n { path: 'onnx/model_int8.onnx', approxBytes: 349_000_000 },\n ],\n labels: DEFAULT_PII_LABELS,\n languages: ['multi'],\n },\n {\n id: 'gliner-small',\n name: 'GLiNER small English, int8 (~183 MB)',\n arch: 'gliner1',\n repo: 'onnx-community/gliner_small-v2.1',\n files: [\n { path: 'gliner_config.json', approxBytes: 731 },\n { path: 'onnx/model_quantized.onnx', approxBytes: 183_403_734 },\n ],\n labels: DEFAULT_PII_LABELS,\n languages: ['en'],\n },\n {\n // Same GLiNER1 span-enumeration architecture and ONNX I/O as the models\n // above, on an LFM2.5-350M backbone converted to bidirectional. Its ONNX\n // file sits at the repo root, not under onnx/.\n id: 'stabrise-pii-multi',\n name: 'StabRise PII multilingual, int8 (~404 MB)',\n arch: 'gliner1',\n repo: 'StabRise/pii-detection-en-fr-ge-it-es',\n files: [\n { path: 'config.json', approxBytes: 3_417 },\n { path: 'model_int8.model', approxBytes: 403_923_207 },\n ],\n labels: DEFAULT_PII_LABELS,\n languages: ['en', 'fr', 'de', 'it', 'es'],\n private: true,\n },\n {\n // Opt-in only. The published weights are fp32 and total roughly 1.2 GB,\n // which is not a reasonable browser download; there is no int8 or q4\n // variant on the Hub. Available for desktop-wrapped or kiosk builds.\n //\n // Pinned to WASM: onnxruntime-web's WebGPU backend silently drops\n // entities on this architecture. GLiNER2's dynamic span-gather and\n // count_embed ops fall back to CPU mid-graph, and the resulting\n // CPU/WebGPU partition boundary corrupts results rather than erroring.\n id: 'stabrise-pii-multi-g2',\n name: 'StabRise PII multilingual GLiNER2, fp32 (~1.2 GB)',\n arch: 'gliner2',\n repo: 'StabRise/pii-multi-g2-v1-onnx',\n files: [\n { path: 'config.json', approxBytes: 48 },\n { path: 'gliner2_config.json', approxBytes: 691 },\n { path: 'onnx/encoder.onnx', approxBytes: 1_111_055_946 },\n { path: 'onnx/span_rep.onnx', approxBytes: 66_111_424 },\n { path: 'onnx/count_embed.onnx', approxBytes: 42_506_885 },\n ],\n labels: GLINER2_PII_LABELS,\n languages: ['en', 'de', 'pl', 'es'],\n private: true,\n executionProviders: ['wasm'],\n },\n])\n\n/** Public, zero-configuration default. */\nexport const DEFAULT_NER_MODEL_ID = 'gliner-multi-pii'\n\nexport function getNerModel(id: string): NerModel | undefined {\n return NER_MODELS.find((m) => m.id === id)\n}\n\n/** Total download size in bytes, for a progress estimate before fetching. */\nexport function modelSizeBytes(model: NerModel): number {\n return model.files.reduce((sum, f) => sum + (f.approxBytes ?? 0), 0)\n}\n","/**\n * Tokenizer loading and adaptation for the GLiNER runtimes.\n *\n * `@huggingface/transformers` provides the tokenizer only; no model runs\n * through it. Its remote host is configurable so gated repos can be proxied\n * through the consuming application's own origin.\n */\n\nimport { getConfig } from '../core/config.js'\nimport { extractTokenIds } from './vendor/math.js'\n\ntype Transformers = typeof import('@huggingface/transformers')\ntype PretrainedTokenizer = Awaited<ReturnType<Transformers['AutoTokenizer']['from_pretrained']>>\n\nlet modulePromise: Promise<Transformers> | null = null\n\nasync function loadTransformers(): Promise<Transformers> {\n if (modulePromise) return modulePromise\n\n modulePromise = (async () => {\n let mod: Transformers\n try {\n mod = await import('@huggingface/transformers')\n } catch (cause) {\n throw new Error(\n '@huggingface/transformers is required for NER tokenization. Install it: npm i @huggingface/transformers',\n { cause }\n )\n }\n\n const { hf } = getConfig()\n // Tokenizers come over HTTP; nothing is resolved from the filesystem.\n mod.env.allowLocalModels = false\n if (hf.remoteHost) mod.env.remoteHost = hf.remoteHost\n if (hf.remotePathTemplate) mod.env.remotePathTemplate = hf.remotePathTemplate\n return mod\n })()\n\n return modulePromise\n}\n\n/** Reset the cached module. Tests only. */\nexport function resetTransformers(): void {\n modulePromise = null\n}\n\nconst tokenizers = new Map<string, Promise<PretrainedTokenizer>>()\n\nexport async function loadTokenizer(repo: string): Promise<PretrainedTokenizer> {\n const existing = tokenizers.get(repo)\n if (existing) return existing\n\n const promise = (async () => {\n const { AutoTokenizer } = await loadTransformers()\n return AutoTokenizer.from_pretrained(repo)\n })()\n\n promise.catch(() => tokenizers.delete(repo))\n tokenizers.set(repo, promise)\n return promise\n}\n\n/** Adapt to the callable form the GLiNER2 runtime expects. */\nexport function toCallableTokenizer(tokenizer: PretrainedTokenizer): (text: string) => number[] {\n return (text: string) => {\n const encoded = tokenizer(text, { add_special_tokens: false }) as {\n input_ids: { tolist(): (bigint | number)[][] | (bigint | number)[] }\n }\n return extractTokenIds(encoded.input_ids.tolist())\n }\n}\n","/**\n * Named-entity recognition over a Document, mirroring ScaleDP's `Ner` stage.\n *\n * Chunks long text, runs a GLiNER backend, and maps the resulting character\n * offsets back onto the OCR boxes so every entity carries its position on the\n * page.\n */\n\nimport { getConfig } from '../core/config.js'\nimport { NerError } from '../core/errors.js'\nimport { ensureModelFiles } from '../core/model-cache.js'\nimport { BASE_STAGE_DEFAULTS, type BaseStageParams, resolveParams } from '../core/params.js'\nimport { type Row, Stage } from '../core/pipeline.js'\nimport type { Box } from '../schemas/box.js'\nimport type { Document } from '../schemas/document.js'\nimport { createNerOutput, type Entity, type NerOutput } from '../schemas/entity.js'\nimport type { NerBackend } from './backend.js'\nimport {\n chunkText,\n DEFAULT_CHUNK_LENGTH,\n DEFAULT_CHUNK_STRIDE,\n dedupeSpans,\n normaliseCasing,\n rebaseSpan,\n} from './chunking.js'\nimport { Gliner1Backend } from './gliner1-backend.js'\nimport { Gliner2Backend } from './gliner2-backend.js'\nimport { DEFAULT_NER_MODEL_ID, DEFAULT_PII_LABELS, getNerModel } from './registry.js'\nimport { loadTokenizer } from './tokenizer.js'\nimport type { DecodedSpan } from './vendor/span-decoder.js'\n\nexport interface GlinerNerParams extends BaseStageParams {\n /** Registry id, e.g. 'gliner-multi-pii'. */\n model: string\n /** Entity types to look for. GLiNER scores a label by its prompt text. */\n labels: readonly string[]\n /** Minimum score an entity must reach (0-1). */\n threshold: number\n /** Only keep these entity groups; empty keeps everything. */\n whiteList: readonly string[]\n chunkLength: number\n chunkStride: number\n /**\n * Title-case runs of capitals before inference. GLiNER1 models are cased\n * and scanned documents are often set in all caps.\n */\n normaliseCasing: boolean\n}\n\nexport const GLINER_NER_DEFAULTS: GlinerNerParams = Object.freeze({\n ...BASE_STAGE_DEFAULTS,\n inputCol: 'text',\n outputCol: 'ner',\n keepInputData: true,\n model: DEFAULT_NER_MODEL_ID,\n labels: DEFAULT_PII_LABELS,\n threshold: 0.5,\n whiteList: [] as readonly string[],\n chunkLength: DEFAULT_CHUNK_LENGTH,\n chunkStride: DEFAULT_CHUNK_STRIDE,\n normaliseCasing: true,\n})\n\n/**\n * Map each character of the joined document text to the box it came from.\n *\n * Built from the *actual* text the OCR stage produced rather than assuming one\n * separator per box. Python derives the mapping from `len(box.text) + 1`, which\n * silently drifts whenever `keepFormatting` inserted several spaces or a\n * newline, shifting every entity's boxes after the first wide gap.\n */\nexport function buildCharToBoxMap(text: string, boxes: readonly Box[]): Int32Array {\n const mapping = new Int32Array(text.length).fill(-1)\n let cursor = 0\n\n for (const [index, box] of boxes.entries()) {\n if (box.text.length === 0) continue\n const found = text.indexOf(box.text, cursor)\n if (found === -1) continue\n mapping.fill(index, found, found + box.text.length)\n cursor = found + box.text.length\n }\n return mapping\n}\n\n/** Boxes a character range touches, in document order and without repeats. */\nexport function boxesForRange(mapping: Int32Array, boxes: readonly Box[], start: number, end: number): Box[] {\n const seen = new Set<number>()\n const out: Box[] = []\n for (let i = Math.max(0, start); i < Math.min(end, mapping.length); i++) {\n const index = mapping[i] as number\n if (index < 0 || seen.has(index)) continue\n seen.add(index)\n const box = boxes[index]\n if (box) out.push(box)\n }\n return out\n}\n\nexport class GlinerNer extends Stage<GlinerNerParams> {\n readonly name = 'GlinerNer'\n\n private backend: NerBackend | null = null\n private loading: Promise<NerBackend> | null = null\n\n constructor(options: Partial<GlinerNerParams> = {}) {\n super(\n resolveParams(GLINER_NER_DEFAULTS, options, {\n threshold: (value) => {\n if (!(value >= 0 && value <= 1)) {\n throw new RangeError(`threshold must be between 0 and 1, received ${value}`)\n }\n },\n model: (value) => {\n if (!getNerModel(value)) {\n throw new RangeError(`Unknown NER model \"${value}\". See NER_MODELS for valid ids.`)\n }\n },\n })\n )\n }\n\n override async init(): Promise<void> {\n await this.getBackend()\n }\n\n private getBackend(): Promise<NerBackend> {\n if (this.backend) return Promise.resolve(this.backend)\n if (this.loading) return this.loading\n\n this.loading = (async () => {\n const model = getNerModel(this.params.model)\n if (!model) throw new NerError(`Unknown model ${this.params.model}`, this.name)\n if (model.private && !getConfig().auth) {\n throw new NerError(\n `Model ${model.id} lives in a private repo. Supply a token via configure({ auth }).`,\n this.name\n )\n }\n\n const [files, tokenizer] = await Promise.all([\n ensureModelFiles({ repo: model.repo, files: model.files }),\n loadTokenizer(model.repo),\n ])\n\n const backend: NerBackend = model.arch === 'gliner2' ? new Gliner2Backend() : new Gliner1Backend()\n await backend.load(files, {\n tokenizer,\n executionProviders: model.executionProviders ?? getConfig().executionProviders,\n })\n this.backend = backend\n return backend\n })()\n\n this.loading.catch(() => {\n this.loading = null\n })\n return this.loading\n }\n\n protected async apply(input: unknown, row: Row): Promise<NerOutput> {\n const document = input as Document | undefined\n if (!document || typeof document.text !== 'string') {\n throw new NerError('Expected a Document with text', this.name)\n }\n if (document.exception) {\n throw new NerError(`Upstream stage failed: ${document.exception}`, this.name)\n }\n\n const entities = await this.extract(document)\n return createNerOutput({\n path: String(row[this.params.pathCol] ?? document.path ?? 'memory'),\n entities,\n json: JSON.stringify(entities),\n })\n }\n\n /** Run NER over a document and attach boxes to every entity found. */\n async extract(document: Document): Promise<Entity[]> {\n const { labels, threshold, whiteList, chunkLength, chunkStride } = this.params\n if (document.text.trim().length === 0 || labels.length === 0) return []\n\n const backend = await this.getBackend()\n // Casing is normalised length-preservingly, so offsets stay valid\n // against the ORIGINAL text -- entity strings are re-sliced from it.\n const source = this.params.normaliseCasing ? normaliseCasing(document.text) : document.text\n\n const spans: DecodedSpan[] = []\n for (const chunk of chunkText(source, chunkLength, chunkStride)) {\n const found = await backend.extract(chunk.text, labels, threshold)\n for (const span of found) spans.push(rebaseSpan(span, chunk.offset))\n }\n\n const mapping = buildCharToBoxMap(document.text, document.bboxes)\n const allowed = new Set(whiteList)\n\n return dedupeSpans(spans)\n .filter((span) => allowed.size === 0 || allowed.has(span.label))\n .map((span) => ({\n entity_group: span.label,\n score: span.score,\n // Re-slice from the original text so the reported word keeps its\n // real casing, not the normalised form the model saw.\n word: document.text.slice(span.start, span.end),\n start: span.start,\n end: span.end,\n boxes: boxesForRange(mapping, document.bboxes, span.start, span.end),\n }))\n }\n\n protected onError(message: string, row: Row): NerOutput {\n return createNerOutput({\n path: String(row[this.params.pathCol] ?? 'memory'),\n exception: message,\n })\n }\n\n override async dispose(): Promise<void> {\n await this.backend?.dispose()\n this.backend = null\n this.loading = null\n }\n}\n"],"mappings":";;;;;;;AAaA,MAAa,uBAAuB;;AAEpC,MAAa,uBAAuB;AAQpC,SAAgB,UACZ,MACA,YAAA,KACA,SAAA,KACO;CACP,IAAI,UAAU,GAAG,MAAM,IAAI,WAAW,qCAAqC,QAAQ;CACnF,IAAI,aAAa,GAAG,MAAM,IAAI,WAAW,wCAAwC,WAAW;CAC5F,IAAI,KAAK,WAAW,GAAG,OAAO,CAAC;CAC/B,IAAI,KAAK,UAAU,WAAW,OAAO,CAAC;EAAE;EAAM,QAAQ;CAAE,CAAC;CAEzD,MAAM,SAAkB,CAAC;CACzB,KAAK,IAAI,SAAS,GAAG,SAAS,KAAK,QAAQ,UAAU,QAAQ;EACzD,MAAM,QAAQ,KAAK,MAAM,QAAQ,SAAS,SAAS;EACnD,OAAO,KAAK;GAAE,MAAM;GAAO;EAAO,CAAC;EAGnC,IAAI,MAAM,SAAS,WAAW;CAClC;CACA,OAAO;AACX;;AAGA,SAAgB,WAAW,MAAmB,QAA6B;CACvE,OAAO;EAAE,GAAG;EAAM,OAAO,KAAK,QAAQ;EAAQ,KAAK,KAAK,MAAM;CAAO;AACzE;;;;;AAMA,SAAgB,YAAY,OAA8C;CACtE,MAAM,uBAAO,IAAI,IAAyB;CAC1C,KAAK,MAAM,QAAQ,OAAO;EACtB,MAAM,MAAM,GAAG,KAAK,MAAM,GAAG,KAAK,IAAI,GAAG,KAAK;EAC9C,MAAM,WAAW,KAAK,IAAI,GAAG;EAC7B,IAAI,CAAC,YAAY,KAAK,QAAQ,SAAS,OAAO,KAAK,IAAI,KAAK,IAAI;CACpE;CACA,OAAO,CAAC,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAC9D;;AAGA,SAAS,eAAe,MAAsB;CAC1C,IAAI,QAAQ;CACZ,IAAI,QAAQ;CACZ,KAAK,MAAM,QAAQ,MACf,IAAI,QAAQ,OAAO,QAAQ,KAAK;MAC3B,IAAI,QAAQ,OAAO,QAAQ,KAAK;MAChC,IAAI,SAAS,KAAK,YAAY,GAAG;MACjC,IAAI,SAAS,KAAK,YAAY,GAAG;CAE1C,OAAO,QAAQ,UAAU,IAAI,IAAI,SAAS,QAAQ;AACtD;AAEA,MAAa,iBAAiB;AAE9B,SAAgB,kBAAkB,MAAuB;CACrD,OAAO,eAAe,IAAI,IAAI;AAClC;;;;;;;;;AAUA,SAAgB,sBAAsB,MAAsB;CACxD,OAAO,KAAK,QAAQ,oCAAoC,SAAS;EAC7D,MAAM,SAAU,KAAK,KAAgB,KAAK,MAAM,CAAC,CAAC,CAAC,YAAY;EAC/D,OAAO,OAAO,WAAW,KAAK,SAAS,SAAS;CACpD,CAAC;AACL;;AAGA,SAAgB,gBAAgB,MAAsB;CAClD,OAAO,kBAAkB,IAAI,IAAI,sBAAsB,IAAI,IAAI;AACnE;;;;;ACjGA,SAAgB,QAAQ,GAAmB;CACvC,IAAI,KAAK,GAAG,OAAO,KAAK,IAAI,KAAK,IAAI,CAAC,CAAC;CACvC,MAAM,OAAO,KAAK,IAAI,CAAC;CACvB,OAAO,QAAQ,IAAI;AACvB;AAEA,SAAgB,QAAQ,QAAyC;CAC7D,IAAI,MAAM,OAAO;CACjB,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;EACpC,MAAM,IAAI,OAAO;EACjB,IAAI,IAAI,KAAK,MAAM;CACvB;CACA,MAAM,MAAM,IAAI,aAAa,OAAO,MAAM;CAC1C,IAAI,MAAM;CACV,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;EACpC,MAAM,IAAI,KAAK,IAAK,OAAO,KAAgB,GAAG;EAC9C,IAAI,KAAK;EACT,OAAO;CACX;CACA,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK,IAAI,KAAM,IAAI,KAAgB;CACnE,OAAO;AACX;;AAGA,SAAgB,WACZ,QACA,WACA,YACY;CACZ,MAAM,MAAM,IAAI,aAAa,UAAU,SAAS,UAAU;CAC1D,KAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;EACvC,MAAM,OAAQ,UAAU,KAAgB;EACxC,IAAI,IAAI,OAAO,SAAS,MAAM,OAAO,UAAU,GAAG,IAAI,UAAU;CACpE;CACA,OAAO;AACX;;AAGA,SAAgB,UACZ,QACA,UACA,UACA,YACY;CACZ,MAAM,OAAO,WAAW;CACxB,OAAO,OAAO,MAAM,MAAM,OAAO,WAAW,UAAU;AAC1D;;AAGA,SAAgB,gBAAgB,cAAqE;CAIjG,QAHa,MAAM,QAAQ,aAAa,EAAE,IACnC,aAAuC,KAAK,IAC5C,aAAA,CACK,KAAK,MAAO,OAAO,MAAM,WAAW,OAAO,CAAC,IAAI,CAAE;AAClE;;;;;;;;;;;;ACtCA,SAAgB,gBAAgB,WAAmD;CAC/E,MAAM,UAAU,SACZ,gBAAgB,UAAU,MAAM,EAAE,oBAAoB,KAAK,CAAC,CAAC,CAAC,UAAU,OAAO,CAAC;CAEpF,MAAM,QAAQ,OAAO,GAAG;CACxB,IAAI,MAAM,SAAS,GACf,MAAM,IAAI,SACN,mEACA,iBACJ;CAEJ,OAAO;EAAE;EAAQ,YAAY,MAAM;EAAc,YAAY,MAAM,MAAM,SAAS;CAAa;AACnG;;;;;;;;;;;;ACFA,MAAM,mBAAmB;AAEzB,SAAS,aACL,QACA,MACA,QACA,MACA,aACA,iBACO;CAEP,IAAI,WAAW,UAAU,SAAS,MAAM,OAAO,CAAC;CAChD,IAAI,SAAS,QAAQ,SAAS,MAAM,OAAO;CAC3C,IAAI,aACgB;MAAA,UAAU,UAAU,QAAQ,QAAU,UAAU,UAAU,QAAQ,MACtE,OAAO;CAAA;CAEvB,OAAO;AACX;;AAGA,SAAS,aAAa,OAAsB,SAAkB,YAAoC;CAC9F,MAAM,UAAU,CAAC,GAAG,KAAK,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;CAC3D,MAAM,OAAsB,CAAC;CAC7B,KAAK,MAAM,QAAQ,SAIf,IAAI,CAHa,KAAK,MAAM,UACxB,aAAa,KAAK,OAAO,KAAK,KAAK,MAAM,OAAO,MAAM,KAAK,CAAC,SAAS,UAAU,CAEvE,GAAG,KAAK,KAAK,IAAI;CAEjC,OAAO,KAAK,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAChD;AAEA,SAAgB,YACZ,QACA,QAUA,UAAyB,CAAC,GACX;CACf,MAAM,YAAY,QAAQ,aAAa;CACvC,MAAM,UAAU,QAAQ,WAAW;CACnC,MAAM,aAAa,QAAQ,cAAc;CAEzC,MAAM,EAAE,WAAW,aAAa,UAAU,gBAAgB;CAC1D,MAAM,cAAc,cAAc,WAAW;CAC7C,MAAM,cAAc,WAAW;CAE/B,MAAM,QAAyB,MAAM,KAAK,EAAE,QAAQ,UAAU,SAAS,CAAC,CAAC;CAEzE,KAAK,IAAI,QAAQ,GAAG,QAAQ,OAAO,QAAQ,SAAS;EAChD,MAAM,QAAQ,QAAQ,OAAO,MAAgB;EAC7C,IAAI,QAAQ,WAAW;EAEvB,MAAM,WAAW,KAAK,MAAM,QAAQ,WAAW;EAC/C,MAAM,YAAY,KAAK,MAAM,QAAQ,WAAW,IAAI;EACpD,MAAM,UAAU,YAAa,KAAK,MAAM,QAAQ,WAAW,IAAI;EAC/D,MAAM,YAAY,QAAQ;EAE1B,MAAM,QAAQ,OAAO,WAAW;EAChC,IAAI,CAAC,SAAS,aAAa,MAAM,UAAU,WAAW,MAAM,QAAQ;EAEpE,MAAM,QAAS,MAAM,UAAU,CAAe;EAC9C,MAAM,MAAO,MAAM,QAAQ,CAAe;EAC1C,MAAM,OAAO,OAAO,MAAM,aAAa;EAEtC,MAAO,SAAS,CAAmB,KAAK;GACrC,MAAM,KAAK,MAAM,OAAO,GAAG;GAC3B,OAAO,OAAO,UAAU,YAAY,qBAAqB;GACzD;GACA;GACA;EACJ,CAAC;CACL;CAEA,OAAO,MAAM,KAAK,UAAU,aAAa,OAAO,SAAS,UAAU,CAAC;AACxE;;;;;;;;;;;;ACtGA,MAAa,eAAe;;;;;AAM5B,MAAa,oBACT;;;;;;;AAUJ,SAAgB,WAAW,MAAc,UAAkB,cAA2B;CAClF,MAAM,QAAQ,IAAI,OAAO,QAAQ,QAAQ,QAAQ,KAAK;CACtD,MAAM,MAAmB,CAAC;CAC1B,SAAS;EACL,MAAM,QAAQ,MAAM,KAAK,IAAI;EAC7B,IAAI,UAAU,MAAM;EACpB,IAAI,KAAK;GAAC,MAAM;GAAI,MAAM;GAAO,MAAM;EAAS,CAAC;CACrD;CACA,OAAO;AACX;;;;;;;;;AC3BA,MAAM,MAAM;AA2BZ,SAAS,IAAO,QAAe,OAAiB;CAC5C,MAAM,MAAM,KAAK,IAAI,GAAG,OAAO,KAAK,MAAM,EAAE,MAAM,CAAC;CACnD,OAAO,OAAO,KAAK,MAAM,CAAC,GAAG,GAAG,GAAG,IAAI,MAAS,MAAM,EAAE,MAAM,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC;AAChF;AAEA,IAAa,gBAAb,MAA2B;CAEF;CACA;CAFrB,YACI,QACA,WACF;EAFmB,KAAA,SAAA;EACA,KAAA,YAAA;CAClB;CAEH,QAAQ,OAA0B,QAA2C;EACzE,MAAM,aAAa,MAAM,KAAK,SAAS,WAAW,IAAI,CAAC;EAEvD,MAAM,YAAoC,CAAC;EAC3C,KAAK,MAAM,CAAC,GAAG,UAAU,OAAO,QAAQ,GAAG,UAAU,IAAI,KAAK;EAE9D,MAAM,WAAuB,CAAC;EAC9B,MAAM,iBAA6B,CAAC;EACpC,MAAM,aAAyB,CAAC;EAChC,MAAM,cAAwB,CAAC;EAC/B,MAAM,WAAyB,CAAC;EAChC,MAAM,YAAyB,CAAC;EAEhC,KAAK,MAAM,SAAS,YAAY;GAC5B,YAAY,KAAK,MAAM,MAAM;GAG7B,MAAM,SAAmB,CAAC;GAC1B,KAAK,MAAM,SAAS,QAAQ,OAAO,KAAK,KAAK,OAAO,UAAU,KAAK;GACnE,OAAO,KAAK,KAAK,OAAO,QAAQ;GAChC,MAAM,WAAW,CAAC,GAAG,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC;GAErD,MAAM,MAAgB,CAAC,KAAK,UAAU,UAAU;GAChD,MAAM,YAAsB,CAAC,CAAC;GAI9B,MAAM,YAAsB,CAAC,GAAG;GAChC,IAAI,cAAc;GAElB,KAAK,MAAM,CAAC,SAAS,SAAS,SAAS,QAAQ,GAAG;IAG9C,MAAM,YAAY,KAAK,UAAU,OAAO,IAAI,CAAC,CAAC,MAAM,GAAG,EAAE;IACzD,KAAK,MAAM,CAAC,UAAU,OAAO,UAAU,QAAQ,GAAG;KAC9C,IAAI,KAAK,EAAE;KACX,UAAU,KAAK,CAAC;KAChB,IAAI,UAAU,OAAO,QAAQ,UAAU,KAAK,GAAG;UAC1C,IAAI,aAAa,GAAG,UAAU,KAAK,aAAa;UAChD,UAAU,KAAK,GAAG;IAC3B;GACJ;GACA,IAAI,KAAK,KAAK,UAAU,UAAU;GAClC,UAAU,KAAK,CAAC;GAChB,UAAU,KAAK,GAAG;GAElB,SAAS,KAAK,GAAG;GACjB,eAAe,KAAK,SAAS;GAC7B,WAAW,KAAK,SAAS;GAEzB,MAAM,UAAsB,CAAC;GAC7B,MAAM,WAAsB,CAAC;GAC7B,KAAK,IAAI,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SACtC,KAAK,IAAI,QAAQ,GAAG,QAAQ,KAAK,OAAO,UAAU,SAAS;IACvD,MAAM,MAAM,KAAK,IAAI,QAAQ,OAAO,MAAM,SAAS,CAAC;IACpD,QAAQ,KAAK,CAAC,OAAO,GAAG,CAAC;IACzB,SAAS,KAAK,MAAM,MAAM,MAAM;GACpC;GAEJ,SAAS,KAAK,OAAO;GACrB,UAAU,KAAK,QAAQ;EAC3B;EAEA,MAAM,WAAW,KAAK,IAAI,GAAG,SAAS,KAAK,MAAM,EAAE,MAAM,CAAC;EAC1D,OAAO;GACH,UAAU,IAAI,UAAU,GAAG;GAC3B,gBAAgB,IAAI,gBAAgB,GAAG;GACvC,YAAY,IAAI,YAAY,GAAG;GAC/B;GACA,UAAU,SAAS,KAAK,MAAM,CAC1B,GAAG,GACH,GAAG,MAAM,KAAK,EAAE,QAAQ,WAAW,EAAE,OAAO,SAAS,CAAC,KAAK,GAAG,CAAC,CACnE,CAAC;GACD,WAAW,IAAI,WAAW,KAAK;GAC/B;GACA;EACJ;CACJ;AACJ;;;;;;;;;;;;;;;;;ACtGA,MAAM,oBAAoB;AAC1B,MAAM,oBAAoB;AAC1B,MAAM,oBAAoB;AAE1B,MAAM,oBAAoB,CAAC,sBAAsB,aAAa;;AAE9D,MAAM,mBAAmB,CAAC,SAAS,QAAQ;AAE3C,MAAM,gBAAgB;AAEtB,SAAS,WAAW,OAAiC;CACjD,KAAK,MAAM,QAAQ,mBAAmB;EAClC,MAAM,MAAM,MAAM;EAClB,IAAI,CAAC,KAAK;EACV,IAAI;GACA,MAAM,SAAS,KAAK,MAAM,IAAI,YAAY,CAAC,CAAC,OAAO,GAAG,CAAC;GACvD,OAAO;IACH,UAAU,OAAO,OAAO,SAAS,KAAK;IACtC,UAAU,OAAO,OAAO,aAAa,iBAAiB;IACtD,UAAU,OAAO,OAAO,aAAa,iBAAiB;GAC1D;EACJ,QAAQ,CAER;CACJ;CACA,OAAO;EACH,UAAU;EACV,UAAU;EACV,UAAU;CACd;AACJ;AAEA,SAAS,eAAe,OAAgC;CACpD,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,KAAK,GAC5C,IAAI,iBAAiB,MAAM,QAAQ,KAAK,SAAS,GAAG,CAAC,GAAG,OAAO;CAEnE,MAAM,IAAI,SAAS,mDAAmD,gBAAgB;AAC1F;AAEA,IAAa,iBAAb,MAAkD;CAC9C,OAAgB;CAEhB,UAAqE;CACrE,YAA0C;CAC1C,SAAsC;CAEtC,MAAM,KAAK,OAAmB,SAA+C;EACzE,KAAK,SAAS,WAAW,KAAK;EAC9B,KAAK,YAAY,IAAI,cACjB,KAAK,QACL,gBAAgB,QAAQ,SAAoC,CAChE;EACA,KAAK,UAAU,MAAM,cAAc,eAAe,KAAK,GAAG,EACtD,oBAAoB,QAAQ,mBAChC,CAAC;CACL;CAEA,MAAM,QAAQ,MAAc,QAA2B,WAA2C;EAG9F,MAAM,UAAU,KAAK;EACrB,MAAM,YAAY,KAAK;EACvB,MAAM,SAAS,KAAK;EACpB,IAAI,CAAC,WAAW,CAAC,aAAa,CAAC,QAC3B,MAAM,IAAI,SAAS,8BAA8B,gBAAgB;EAErE,IAAI,OAAO,WAAW,GAAG,OAAO,CAAC;EAEjC,MAAM,QAAQ,UAAU,QAAQ,CAAC,IAAI,GAAG,MAAM;EAC9C,MAAM,QAAQ,MAAM,WAAW,MAAM,CAAC;EACtC,IAAI,MAAM,WAAW,GAAG,OAAO,CAAC;EAEhC,MAAM,EAAE,WAAW,MAAM,OAAO;EAChC,MAAM,aAAc,MAAM,SAAS,EAAE,CAAc;EACnD,MAAM,YAAa,MAAM,SAAS,EAAE,CAAgB;EAEpD,MAAM,OAAO,WAAqB,cAAc,KAAK,QAAQ,MAAM;EAEnE,MAAM,QAA0D;GAC5D,WAAW,IAAI,OAAO,SAAS,IAAI,MAAM,SAAS,KAAK,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC;GAC1E,gBAAgB,IAAI,OAAO,SAAS,IAAI,MAAM,eAAe,KAAK,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC;GACrF,YAAY,IAAI,OAAO,SAAS,IAAI,MAAM,WAAW,KAAK,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC;GAC7E,cAAc,IAAI,OAAO,SAAS,IAAI,MAAM,WAAW,GAAG,CAAC,GAAG,CAAC,CAAC;GAChE,UAAU,IAAI,OAAO,SAAS,IAAI,MAAM,SAAS,KAAK,CAAC,CAAC,GAAG;IAAC;IAAG;IAAW;GAAC,CAAC;GAC5E,WAAW,IAAI,OACX,QACA,WAAW,KAAK,MAAM,UAAU,KAAK,IAAI,MAAO,IAAI,IAAI,CAAE,GAC1D,CAAC,GAAG,SAAS,CACjB;EACJ;EAIA,MAAM,SAA2D,CAAC;EAClE,KAAK,MAAM,QAAQ,QAAQ,YAAY;GACnC,MAAM,SAAS,MAAM;GACrB,IAAI,CAAC,QAAQ,MAAM,IAAI,SAAS,gCAAgC,KAAK,IAAI,gBAAgB;GACzF,OAAO,QAAQ;EACnB;EAEA,MAAM,UAAU,MAAM,QAAQ,IAAI,MAAM;EACxC,MAAM,UAAU,QAAQ,kBAAkB,QAAQ,QAAQ,YAAY,MAAM,IAAA,EAAM;EAClF,IAAI,CAAC,QAAQ,MAAM,IAAI,SAAS,4BAA4B,gBAAgB;EAE5E,MAAM,CAAC,QAAQ,CAAC,KAAK,YACjB,QACA;GACI,WAAW;GACX,aAAa,MAAM;GACnB,UAAU,OAAO;GACjB,aAAa,OAAO;GACpB,OAAO,CAAC,IAAI;GACZ,YAAY,MAAM;GAClB,WAAW,MAAM;EACrB,GACA;GAAE;GAAW,SAAS;GAAM,YAAY;EAAM,CAClD;EACA,OAAO;CACX;CAEA,MAAM,UAAyB;EAC3B,MAAM,KAAK,SAAS,QAAQ;EAC5B,KAAK,UAAU;EACf,KAAK,YAAY;EACjB,KAAK,SAAS;CAClB;AACJ;;;;;;;;;;;ACvIA,SAAgB,cACZ,QACA,UAC6D;CAC7D,MAAM,YAAsB,CAAC;CAC7B,MAAM,UAAoB,CAAC;CAC3B,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KACxB,KAAK,IAAI,IAAI,GAAG,IAAI,UAAU,KAAK;EAC/B,MAAM,UAAU,IAAI,IAAI;EACxB,UAAU,KAAK,UAAU,IAAI,CAAC;EAC9B,QAAQ,KAAK,UAAU,IAAI,IAAI,CAAC;CACpC;CAEJ,OAAO;EAAE;EAAW;EAAS,WAAW,UAAU;CAAO;AAC7D;;AAGA,SAAgB,wBACZ,SACA,UACA,WACA,YACA,YACY;CACZ,MAAM,SAAS,IAAI,aAAa,YAAY,UAAU;CACtD,KAAK,IAAI,IAAI,GAAG,IAAI,WAAW,KAAK;EAChC,MAAM,aAAa,IAAI;EACvB,KAAK,IAAI,IAAI,GAAG,IAAI,YAAY,KAAK;GACjC,MAAM,cAAc,IAAI;GACxB,IAAI,MAAM;GACV,KAAK,IAAI,IAAI,GAAG,IAAI,YAAY,KAC5B,OAAQ,QAAQ,aAAa,KAAiB,SAAS,cAAc;GAEzE,OAAO,IAAI,aAAa,KAAK,QAAQ,GAAG;EAC5C;CACJ;CACA,OAAO;AACX;;AAUA,SAAgB,eACZ,WACA,WACA,QACA,aACA,MACA,WACa;CACb,MAAM,EAAE,QAAQ,eAAe,aAAa,cAAc;CAC1D,MAAM,aAAa,OAAO;CAC1B,MAAM,WAA0B,CAAC;CAEjC,KAAK,IAAI,IAAI,GAAG,IAAI,WAAW,KAAK;EAChC,MAAM,YAAY,cAAc;EAChC,MAAM,UAAU,YAAY;EAC5B,IAAI,aAAa,aAAa,WAAW,WAAW;EAEpD,KAAK,IAAI,IAAI,GAAG,IAAI,YAAY,KAAK;GACjC,MAAM,QAAQ,OAAO,IAAI,aAAa;GACtC,IAAI,QAAQ,WAAW;GAEvB,MAAM,QAAS,YAAY,UAAU,CAAgB;GACrD,MAAM,MAAO,YAAY,QAAQ,CAAgB;GACjD,SAAS,KAAK;IACV,MAAM,KAAK,MAAM,OAAO,GAAG;IAC3B,OAAO,OAAO;IACd;IACA;IACA;GACJ,CAAC;EACL;CACJ;CACA,OAAO,oBAAoB,QAAQ;AACvC;;;;;;AAOA,SAAS,oBAAoB,UAAiD;CAC1E,MAAM,UAAU,CAAC,GAAG,QAAQ,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;CAC9D,MAAM,OAAsB,CAAC;CAC7B,KAAK,MAAM,UAAU,SAIjB,IAAI,CAHa,KAAK,MACjB,UAAU,OAAO,UAAU,MAAM,SAAS,OAAO,QAAQ,MAAM,OAAO,OAAO,MAAM,MAAM,KAElF,GAAG,KAAK,KAAK,MAAM;CAEnC,OAAO,KAAK,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAChD;;;;;;;;;;;;;;;;;;;;;AC7EA,MAAM,cAAc;AACpB,MAAM,sBAAsB;AAE5B,MAAM,UAAU;AAChB,MAAM,UAAU;AAChB,MAAM,iBAAiB;AACvB,MAAM,cAAc;AACpB,MAAM,eAAe;AACrB,MAAM,gBAAgB;AAQtB,SAAS,WAAc,QAAiC,MAAiB;CACrE,IAAI,CAAC,QAAQ,MAAM,IAAI,SAAS,oBAAoB,QAAQ,gBAAgB;CAC5E,OAAO,KAAK,MAAM,IAAI,YAAY,CAAC,CAAC,OAAO,MAAM,CAAC;AACtD;AAEA,IAAa,iBAAb,MAAkD;CAC9C,OAAgB;CAEhB,SAAuC;CACvC,UAAqE;CACrE,UAAqE;CACrE,aAAwE;CACxE,WAAwD;CAExD,MAAM,KAAK,OAAmB,SAA+C;EACzE,MAAM,cAAc,WAAoC,MAAM,cAAc,WAAW;EACvF,MAAM,UAAU,WACZ,MAAM,sBACN,mBACJ;EACA,IAAI,OAAO,YAAY,gBAAgB,UACnC,MAAM,IAAI,SAAS,GAAG,YAAY,0BAA0B,gBAAgB;EAEhF,IAAI,OAAO,QAAQ,cAAc,UAC7B,MAAM,IAAI,SAAS,GAAG,oBAAoB,wBAAwB,gBAAgB;EAGtF,KAAK,SAAS;GACV,YAAY,YAAY;GACxB,UAAU,QAAQ;GAClB,eAAe,QAAQ;EAC3B;EAEA,MAAM,YAAY,QAAQ;EAC1B,KAAK,YAAY,SACb,gBAAgB,UAAU,MAAM,EAAE,oBAAoB,MAAM,CAAC,CAAC,CAAC,UAAU,OAAO,CAAC;EAErF,MAAM,SAAS,SAA8B;GACzC,MAAM,QAAQ,MAAM;GACpB,IAAI,CAAC,OAAO,MAAM,IAAI,SAAS,oBAAoB,QAAQ,gBAAgB;GAC3E,OAAO;EACX;EAKA,MAAM,YAAY,QAAQ,sBAAsB,CAAC,MAAM;EACvD,MAAM,CAAC,SAAS,SAAS,cAAc,MAAM,QAAQ,IAAI;GACrD,cAAc,MAAM,mBAAmB,GAAG,EAAE,oBAAoB,UAAU,CAAC;GAC3E,cAAc,MAAM,oBAAoB,GAAG,EAAE,oBAAoB,UAAU,CAAC;GAC5E,cAAc,MAAM,uBAAuB,GAAG,EAAE,oBAAoB,UAAU,CAAC;EACnF,CAAC;EACD,KAAK,UAAU;EACf,KAAK,UAAU;EACf,KAAK,aAAa;CACtB;CAEA,MAAM,QAAQ,MAAc,QAA2B,WAA2C;EAC9F,MAAM,EAAE,QAAQ,aAAa;EAC7B,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,KAAK,WAAW,CAAC,KAAK,WAAW,CAAC,KAAK,YAChE,MAAM,IAAI,SAAS,8BAA8B,gBAAgB;EAErE,IAAI,KAAK,KAAK,CAAC,CAAC,WAAW,KAAK,OAAO,WAAW,GAAG,OAAO,CAAC;EAE7D,MAAM,SAAS,KAAK,YAAY,MAAM;EACtC,MAAM,QAAQ,KAAK,cAAc,IAAI;EACrC,IAAI,MAAM,YAAY,WAAW,GAAG,OAAO,CAAC;EAE5C,MAAM,YAAY,CAAC,GAAG,OAAO,QAAQ,GAAG,MAAM,MAAM;EACpD,MAAM,SAAS,MAAM,KAAK,OAAO,SAAS;EAE1C,MAAM,kBAAkB,WAAW,QAAQ,OAAO,gBAAgB,OAAO,UAAU;EACnF,MAAM,iBAAiB,UAAU,SAAS,OAAO,OAAO;EACxD,IAAI,mBAAmB,GAAG,OAAO,CAAC;EAElC,MAAM,aAAa,UAAU,QAAQ,OAAO,OAAO,QAAQ,gBAAgB,OAAO,UAAU;EAE5F,MAAM,EAAE,WAAW,SAAS,cAAc,cAAc,MAAM,YAAY,QAAQ,OAAO,QAAQ;EAGjG,MAAM,WAAW,YAAoB,MAAM,oBAAoB,YAAY;EAkB3E,OAAO,eACH;GAAE,QATS,wBACX,MAV8B,KAAK,WACnC,YACA,gBACA,UAAU,IAAI,OAAO,GACrB,QAAQ,IAAI,OAAO,GACnB,SACJ,GAKI,MAJ4B,KAAK,cAAc,iBAAiB,OAAO,MAAM,GAK7E,WACA,OAAO,QACP,OAAO,UAIL;GAAQ,eAAe;GAAW,aAAa;GAAS;EAAU,GACpE,MAAM,YAAY,QAClB,QACA,MAAM,aACN,MACA,SACJ;CACJ;;CAGA,YAAoB,QAA2E;EAC3F,MAAM,SAAS,KAAK;EACpB,MAAM,WAAW,KAAK;EAEtB,MAAM,OAAO,SAAS,WAAW;EACjC,MAAM,QAAQ,SAAS,YAAY;EACnC,MAAM,SAAmB,CAAC,GAAG,MAAM,OAAO,cAAc,QAAkB;EAC1E,OAAO,KAAK,GAAG,SAAS,aAAa,GAAG,GAAG,IAAI;EAE/C,MAAM,iBAA2B,CAAC;EAClC,KAAK,MAAM,SAAS,QAAQ;GAGxB,eAAe,KAAK,OAAO,MAAM;GACjC,OAAO,KAAK,OAAO,cAAc,UAAoB,GAAG,SAAS,KAAK,CAAC;EAC3E;EACA,OAAO,KAAK,GAAG,OAAO,GAAG,OAAO,OAAO,cAAc,eAAyB;EAC9E,OAAO;GAAE;GAAQ;EAAe;CACpC;;;;;;;;CASA,cAAsB,MAIpB;EACE,MAAM,WAAW,KAAK;EACtB,MAAM,SAAmB,CAAC;EAC1B,MAAM,cAA4B,CAAC;EACnC,MAAM,sBAAgC,CAAC;EAEvC,KAAK,MAAM,CAAC,MAAM,OAAO,QAAQ,WAAW,KAAK,YAAY,GAAG,iBAAiB,GAAG;GAChF,YAAY,KAAK,CAAC,OAAO,GAAG,CAAC;GAC7B,oBAAoB,KAAK,OAAO,MAAM;GACtC,OAAO,KAAK,GAAG,SAAS,IAAI,CAAC;EACjC;EACA,OAAO;GAAE;GAAQ;GAAa;EAAoB;CACtD;CAEA,MAAc,OAAO,QAAyC;EAC1D,MAAM,EAAE,WAAW,MAAM,OAAO;EAChC,MAAM,UAAU,KAAK;EACrB,MAAM,SAAS,OAAO;EAMtB,OAAO,YAAY,MAJG,QAAQ,IAAI;GAC9B,WAAW,IAAI,OAAO,SAAS,cAAc,KAAK,QAAQ,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC;GAC9E,gBAAgB,IAAI,OAAO,SAAS,IAAI,cAAc,MAAM,CAAC,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC;EACvF,CAAC,GAC2B,SAAS;CACzC;CAEA,MAAc,WACV,QACA,QACA,WACA,SACA,WACqB;EACrB,MAAM,EAAE,WAAW,MAAM,OAAO;EAChC,MAAM,UAAU,KAAK;EACrB,MAAM,aAAc,KAAK,OAAyB;EAOlD,OAAO,YAAY,MALG,QAAQ,IAAI;GAC9B,eAAe,IAAI,OAAO,WAAW,QAAQ;IAAC;IAAG;IAAQ;GAAU,CAAC;GACpE,gBAAgB,IAAI,OAAO,SAAS,cAAc,KAAK,WAAW,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC;GACzF,cAAc,IAAI,OAAO,SAAS,cAAc,KAAK,SAAS,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC;EACzF,CAAC,GAC2B,UAAU;CAC1C;CAEA,MAAc,cAAc,iBAA+B,YAA2C;EAClG,MAAM,EAAE,WAAW,MAAM,OAAO;EAChC,MAAM,UAAU,KAAK;EACrB,MAAM,aAAc,KAAK,OAAyB;EAKlD,OAAO,YAAY,MAHG,QAAQ,IAAI,EAC9B,kBAAkB,IAAI,OAAO,WAAW,iBAAiB,CAAC,YAAY,UAAU,CAAC,EACrF,CAAC,GAC2B,aAAa;CAC7C;CAEA,MAAM,UAAyB;EAC3B,MAAM,QAAQ,IAAI;GAAC,KAAK,SAAS,QAAQ;GAAG,KAAK,SAAS,QAAQ;GAAG,KAAK,YAAY,QAAQ;EAAC,CAAC;EAChG,KAAK,UAAU;EACf,KAAK,UAAU;EACf,KAAK,aAAa;EAClB,KAAK,SAAS;EACd,KAAK,WAAW;CACpB;AACJ;AAEA,SAAS,YACL,SACA,OACY;CACZ,MAAM,CAAC,QAAQ,OAAO,KAAK,OAAO;CAClC,IAAI,CAAC,MAAM,MAAM,IAAI,SAAS,SAAS,MAAM,sBAAsB,gBAAgB;CACnF,OAAQ,QAAQ,KAAK,CAA4B;AACrD;;;;ACtOA,MAAa,qBAAwC,OAAO,OAAO;CAC/D;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACJ,CAAC;;;;;;;;AASD,MAAa,qBAAwC,OAAO,OAAO;CAC/D;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACJ,CAAC;AAED,MAAa,aAAkC,OAAO,OAAO;CACzD;EACI,IAAI;EACJ,MAAM;EACN,MAAM;EACN,MAAM;EACN,OAAO,CACH;GAAE,MAAM;GAAsB,aAAa;EAAI,GAC/C;GAAE,MAAM;GAAwB,aAAa;EAAY,CAC7D;EACA,QAAQ;EACR,WAAW,CAAC,OAAO;CACvB;CACA;EACI,IAAI;EACJ,MAAM;EACN,MAAM;EACN,MAAM;EACN,OAAO,CACH;GAAE,MAAM;GAAsB,aAAa;EAAI,GAC/C;GAAE,MAAM;GAA6B,aAAa;EAAY,CAClE;EACA,QAAQ;EACR,WAAW,CAAC,IAAI;CACpB;CACA;EAII,IAAI;EACJ,MAAM;EACN,MAAM;EACN,MAAM;EACN,OAAO,CACH;GAAE,MAAM;GAAe,aAAa;EAAM,GAC1C;GAAE,MAAM;GAAoB,aAAa;EAAY,CACzD;EACA,QAAQ;EACR,WAAW;GAAC;GAAM;GAAM;GAAM;GAAM;EAAI;EACxC,SAAS;CACb;CACA;EASI,IAAI;EACJ,MAAM;EACN,MAAM;EACN,MAAM;EACN,OAAO;GACH;IAAE,MAAM;IAAe,aAAa;GAAG;GACvC;IAAE,MAAM;IAAuB,aAAa;GAAI;GAChD;IAAE,MAAM;IAAqB,aAAa;GAAc;GACxD;IAAE,MAAM;IAAsB,aAAa;GAAW;GACtD;IAAE,MAAM;IAAyB,aAAa;GAAW;EAC7D;EACA,QAAQ;EACR,WAAW;GAAC;GAAM;GAAM;GAAM;EAAI;EAClC,SAAS;EACT,oBAAoB,CAAC,MAAM;CAC/B;AACJ,CAAC;;AAGD,MAAa,uBAAuB;AAEpC,SAAgB,YAAY,IAAkC;CAC1D,OAAO,WAAW,MAAM,MAAM,EAAE,OAAO,EAAE;AAC7C;;AAGA,SAAgB,eAAe,OAAyB;CACpD,OAAO,MAAM,MAAM,QAAQ,KAAK,MAAM,OAAO,EAAE,eAAe,IAAI,CAAC;AACvE;;;;;;;;;;ACpJA,IAAI,gBAA8C;AAElD,eAAe,mBAA0C;CACrD,IAAI,eAAe,OAAO;CAE1B,iBAAiB,YAAY;EACzB,IAAI;EACJ,IAAI;GACA,MAAM,MAAM,OAAO;EACvB,SAAS,OAAO;GACZ,MAAM,IAAI,MACN,2GACA,EAAE,MAAM,CACZ;EACJ;EAEA,MAAM,EAAE,OAAO,UAAU;EAEzB,IAAI,IAAI,mBAAmB;EAC3B,IAAI,GAAG,YAAY,IAAI,IAAI,aAAa,GAAG;EAC3C,IAAI,GAAG,oBAAoB,IAAI,IAAI,qBAAqB,GAAG;EAC3D,OAAO;CACX,EAAA,CAAG;CAEH,OAAO;AACX;;AAGA,SAAgB,oBAA0B;CACtC,gBAAgB;AACpB;AAEA,MAAM,6BAAa,IAAI,IAA0C;AAEjE,eAAsB,cAAc,MAA4C;CAC5E,MAAM,WAAW,WAAW,IAAI,IAAI;CACpC,IAAI,UAAU,OAAO;CAErB,MAAM,WAAW,YAAY;EACzB,MAAM,EAAE,kBAAkB,MAAM,iBAAiB;EACjD,OAAO,cAAc,gBAAgB,IAAI;CAC7C,EAAA,CAAG;CAEH,QAAQ,YAAY,WAAW,OAAO,IAAI,CAAC;CAC3C,WAAW,IAAI,MAAM,OAAO;CAC5B,OAAO;AACX;;AAGA,SAAgB,oBAAoB,WAA4D;CAC5F,QAAQ,SAAiB;EAIrB,OAAO,gBAHS,UAAU,MAAM,EAAE,oBAAoB,MAAM,CAGrC,CAAA,CAAQ,UAAU,OAAO,CAAC;CACrD;AACJ;;;;;;;;;;ACrBA,MAAa,sBAAuC,OAAO,OAAO;CAC9D,GAAG;CACH,UAAU;CACV,WAAW;CACX,eAAe;CACf,OAAO;CACP,QAAQ;CACR,WAAW;CACX,WAAW,CAAC;CACZ,aAAA;CACA,aAAA;CACA,iBAAiB;AACrB,CAAC;;;;;;;;;AAUD,SAAgB,kBAAkB,MAAc,OAAmC;CAC/E,MAAM,UAAU,IAAI,WAAW,KAAK,MAAM,CAAC,CAAC,KAAK,EAAE;CACnD,IAAI,SAAS;CAEb,KAAK,MAAM,CAAC,OAAO,QAAQ,MAAM,QAAQ,GAAG;EACxC,IAAI,IAAI,KAAK,WAAW,GAAG;EAC3B,MAAM,QAAQ,KAAK,QAAQ,IAAI,MAAM,MAAM;EAC3C,IAAI,UAAU,IAAI;EAClB,QAAQ,KAAK,OAAO,OAAO,QAAQ,IAAI,KAAK,MAAM;EAClD,SAAS,QAAQ,IAAI,KAAK;CAC9B;CACA,OAAO;AACX;;AAGA,SAAgB,cAAc,SAAqB,OAAuB,OAAe,KAAoB;CACzG,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,MAAa,CAAC;CACpB,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,KAAK,GAAG,IAAI,KAAK,IAAI,KAAK,QAAQ,MAAM,GAAG,KAAK;EACrE,MAAM,QAAQ,QAAQ;EACtB,IAAI,QAAQ,KAAK,KAAK,IAAI,KAAK,GAAG;EAClC,KAAK,IAAI,KAAK;EACd,MAAM,MAAM,MAAM;EAClB,IAAI,KAAK,IAAI,KAAK,GAAG;CACzB;CACA,OAAO;AACX;AAEA,IAAa,YAAb,cAA+B,MAAuB;CAClD,OAAgB;CAEhB,UAAqC;CACrC,UAA8C;CAE9C,YAAY,UAAoC,CAAC,GAAG;EAChD,MACI,cAAc,qBAAqB,SAAS;GACxC,YAAY,UAAU;IAClB,IAAI,EAAE,SAAS,KAAK,SAAS,IACzB,MAAM,IAAI,WAAW,+CAA+C,OAAO;GAEnF;GACA,QAAQ,UAAU;IACd,IAAI,CAAC,YAAY,KAAK,GAClB,MAAM,IAAI,WAAW,sBAAsB,MAAM,iCAAiC;GAE1F;EACJ,CAAC,CACL;CACJ;CAEA,MAAe,OAAsB;EACjC,MAAM,KAAK,WAAW;CAC1B;CAEA,aAA0C;EACtC,IAAI,KAAK,SAAS,OAAO,QAAQ,QAAQ,KAAK,OAAO;EACrD,IAAI,KAAK,SAAS,OAAO,KAAK;EAE9B,KAAK,WAAW,YAAY;GACxB,MAAM,QAAQ,YAAY,KAAK,OAAO,KAAK;GAC3C,IAAI,CAAC,OAAO,MAAM,IAAI,SAAS,iBAAiB,KAAK,OAAO,SAAS,KAAK,IAAI;GAC9E,IAAI,MAAM,WAAW,CAAC,UAAU,CAAC,CAAC,MAC9B,MAAM,IAAI,SACN,SAAS,MAAM,GAAG,oEAClB,KAAK,IACT;GAGJ,MAAM,CAAC,OAAO,aAAa,MAAM,QAAQ,IAAI,CACzC,iBAAiB;IAAE,MAAM,MAAM;IAAM,OAAO,MAAM;GAAM,CAAC,GACzD,cAAc,MAAM,IAAI,CAC5B,CAAC;GAED,MAAM,UAAsB,MAAM,SAAS,YAAY,IAAI,eAAe,IAAI,IAAI,eAAe;GACjG,MAAM,QAAQ,KAAK,OAAO;IACtB;IACA,oBAAoB,MAAM,sBAAsB,UAAU,CAAC,CAAC;GAChE,CAAC;GACD,KAAK,UAAU;GACf,OAAO;EACX,EAAA,CAAG;EAEH,KAAK,QAAQ,YAAY;GACrB,KAAK,UAAU;EACnB,CAAC;EACD,OAAO,KAAK;CAChB;CAEA,MAAgB,MAAM,OAAgB,KAA8B;EAChE,MAAM,WAAW;EACjB,IAAI,CAAC,YAAY,OAAO,SAAS,SAAS,UACtC,MAAM,IAAI,SAAS,iCAAiC,KAAK,IAAI;EAEjE,IAAI,SAAS,WACT,MAAM,IAAI,SAAS,0BAA0B,SAAS,aAAa,KAAK,IAAI;EAGhF,MAAM,WAAW,MAAM,KAAK,QAAQ,QAAQ;EAC5C,OAAO,gBAAgB;GACnB,MAAM,OAAO,IAAI,KAAK,OAAO,YAAY,SAAS,QAAQ,QAAQ;GAClE;GACA,MAAM,KAAK,UAAU,QAAQ;EACjC,CAAC;CACL;;CAGA,MAAM,QAAQ,UAAuC;EACjD,MAAM,EAAE,QAAQ,WAAW,WAAW,aAAa,gBAAgB,KAAK;EACxE,IAAI,SAAS,KAAK,KAAK,CAAC,CAAC,WAAW,KAAK,OAAO,WAAW,GAAG,OAAO,CAAC;EAEtE,MAAM,UAAU,MAAM,KAAK,WAAW;EAGtC,MAAM,SAAS,KAAK,OAAO,kBAAkB,gBAAgB,SAAS,IAAI,IAAI,SAAS;EAEvF,MAAM,QAAuB,CAAC;EAC9B,KAAK,MAAM,SAAS,UAAU,QAAQ,aAAa,WAAW,GAAG;GAC7D,MAAM,QAAQ,MAAM,QAAQ,QAAQ,MAAM,MAAM,QAAQ,SAAS;GACjE,KAAK,MAAM,QAAQ,OAAO,MAAM,KAAK,WAAW,MAAM,MAAM,MAAM,CAAC;EACvE;EAEA,MAAM,UAAU,kBAAkB,SAAS,MAAM,SAAS,MAAM;EAChE,MAAM,UAAU,IAAI,IAAI,SAAS;EAEjC,OAAO,YAAY,KAAK,CAAC,CACpB,QAAQ,SAAS,QAAQ,SAAS,KAAK,QAAQ,IAAI,KAAK,KAAK,CAAC,CAAC,CAC/D,KAAK,UAAU;GACZ,cAAc,KAAK;GACnB,OAAO,KAAK;GAGZ,MAAM,SAAS,KAAK,MAAM,KAAK,OAAO,KAAK,GAAG;GAC9C,OAAO,KAAK;GACZ,KAAK,KAAK;GACV,OAAO,cAAc,SAAS,SAAS,QAAQ,KAAK,OAAO,KAAK,GAAG;EACvE,EAAE;CACV;CAEA,QAAkB,SAAiB,KAAqB;EACpD,OAAO,gBAAgB;GACnB,MAAM,OAAO,IAAI,KAAK,OAAO,YAAY,QAAQ;GACjD,WAAW;EACf,CAAC;CACL;CAEA,MAAe,UAAyB;EACpC,MAAM,KAAK,SAAS,QAAQ;EAC5B,KAAK,UAAU;EACf,KAAK,UAAU;CACnB;AACJ"}
1
+ {"version":3,"file":"ner-BB3vGNxO.js","names":[],"sources":["../src/ner/chunking.ts","../src/ner/vendor/math.ts","../src/ner/tokenizer-types.ts","../src/ner/vendor/span-decoder.ts","../src/ner/vendor/splitter.ts","../src/ner/vendor/span-processor.ts","../src/ner/gliner1-backend.ts","../src/ner/vendor/gliner2-decoder.ts","../src/ner/gliner2-backend.ts","../src/ner/registry.ts","../src/ner/tokenizer.ts","../src/ner/gliner-ner.ts"],"sourcesContent":["/**\n * Long-text chunking and entity de-duplication for NER.\n *\n * Mirrors `Ner.split_text` in Python ScaleDP: fixed-length character windows\n * with a small overlap, offsets rebased onto the original text.\n *\n * Python has no cross-chunk de-duplication, so any entity landing in the\n * overlap is reported twice. That is fixed here.\n */\n\nimport type { DecodedSpan } from './vendor/span-decoder.js'\n\n/** Python's `split_text` default, and what the cloud /ner/text endpoint uses. */\nexport const DEFAULT_CHUNK_LENGTH = 500\n/** 500 - 480 leaves a 20-character overlap so entities on a seam survive. */\nexport const DEFAULT_CHUNK_STRIDE = 480\n\nexport interface Chunk {\n text: string\n /** Character offset of this chunk within the original text. */\n offset: number\n}\n\nexport function chunkText(\n text: string,\n maxLength = DEFAULT_CHUNK_LENGTH,\n stride = DEFAULT_CHUNK_STRIDE\n): Chunk[] {\n if (stride <= 0) throw new RangeError(`stride must be positive, received ${stride}`)\n if (maxLength <= 0) throw new RangeError(`maxLength must be positive, received ${maxLength}`)\n if (text.length === 0) return []\n if (text.length <= maxLength) return [{ text, offset: 0 }]\n\n const chunks: Chunk[] = []\n for (let offset = 0; offset < text.length; offset += stride) {\n const slice = text.slice(offset, offset + maxLength)\n chunks.push({ text: slice, offset })\n // A short slice means the end of the text; stop rather than emit\n // ever-shorter tails.\n if (slice.length < maxLength) break\n }\n return chunks\n}\n\n/** Shift a chunk-local span onto the original text's coordinates. */\nexport function rebaseSpan(span: DecodedSpan, offset: number): DecodedSpan {\n return { ...span, start: span.start + offset, end: span.end + offset }\n}\n\n/**\n * Drop duplicates produced by the chunk overlap, keeping the highest score for\n * each distinct (start, end, label).\n */\nexport function dedupeSpans(spans: readonly DecodedSpan[]): DecodedSpan[] {\n const best = new Map<string, DecodedSpan>()\n for (const span of spans) {\n const key = `${span.start}:${span.end}:${span.label}`\n const existing = best.get(key)\n if (!existing || span.score > existing.score) best.set(key, span)\n }\n return [...best.values()].sort((a, b) => a.start - b.start)\n}\n\n/** Ratio of uppercase to cased letters. */\nfunction uppercaseRatio(text: string): number {\n let upper = 0\n let lower = 0\n for (const char of text) {\n if (char >= 'A' && char <= 'Z') upper++\n else if (char >= 'a' && char <= 'z') lower++\n else if (char !== char.toLowerCase()) upper++\n else if (char !== char.toUpperCase()) lower++\n }\n return upper + lower === 0 ? 0 : upper / (upper + lower)\n}\n\nexport const ALL_CAPS_RATIO = 0.6\n\nexport function isMostlyUppercase(text: string): boolean {\n return uppercaseRatio(text) > ALL_CAPS_RATIO\n}\n\n/**\n * Title-case runs of capitals, preserving length.\n *\n * GLiNER1 models are cased and scanned documents are frequently set in all\n * caps, which reads to the model as unlike anything in training. Length\n * preservation is essential: every character offset the decoder returns is used\n * to index the original text.\n */\nexport function titleCaseAllCapsWords(text: string): string {\n return text.replace(/\\p{Lu}[\\p{Lu}\\p{N}'’-]*\\p{Lu}/gu, (word) => {\n const titled = (word[0] as string) + word.slice(1).toLowerCase()\n return titled.length === word.length ? titled : word\n })\n}\n\n/** Apply the casing fix only when the text is predominantly uppercase. */\nexport function normaliseCasing(text: string): string {\n return isMostlyUppercase(text) ? titleCaseAllCapsWords(text) : text\n}\n","/** Numeric helpers for the GLiNER runtimes. Adapted from @lmoe/gliner-onnx (MIT). */\n\n/** Numerically stable sigmoid: exp(-x) overflows for large negative x. */\nexport function sigmoid(x: number): number {\n if (x >= 0) return 1 / (1 + Math.exp(-x))\n const expX = Math.exp(x)\n return expX / (1 + expX)\n}\n\nexport function softmax(values: ArrayLike<number>): Float32Array {\n let max = Number.NEGATIVE_INFINITY\n for (let i = 0; i < values.length; i++) {\n const v = values[i] as number\n if (v > max) max = v\n }\n const out = new Float32Array(values.length)\n let sum = 0\n for (let i = 0; i < values.length; i++) {\n const e = Math.exp((values[i] as number) - max)\n out[i] = e\n sum += e\n }\n for (let i = 0; i < out.length; i++) out[i] = (out[i] as number) / sum\n return out\n}\n\n/** Gather rows out of a flat [n, hiddenSize] matrix. */\nexport function gatherRows(\n source: Float32Array,\n positions: readonly number[],\n hiddenSize: number\n): Float32Array {\n const out = new Float32Array(positions.length * hiddenSize)\n for (let i = 0; i < positions.length; i++) {\n const from = (positions[i] as number) * hiddenSize\n out.set(source.subarray(from, from + hiddenSize), i * hiddenSize)\n }\n return out\n}\n\n/** Contiguous row slice of a flat [n, hiddenSize] matrix. */\nexport function sliceRows(\n source: Float32Array,\n startRow: number,\n rowCount: number,\n hiddenSize: number\n): Float32Array {\n const from = startRow * hiddenSize\n return source.slice(from, from + rowCount * hiddenSize)\n}\n\n/** Token ids from a transformers.js `tolist()` result, flattened and de-bigint'd. */\nexport function extractTokenIds(tolistResult: (bigint | number)[][] | (bigint | number)[]): number[] {\n const flat = Array.isArray(tolistResult[0])\n ? (tolistResult as (bigint | number)[][]).flat()\n : (tolistResult as (bigint | number)[])\n return flat.map((v) => (typeof v === 'bigint' ? Number(v) : v))\n}\n","/** Shared tokenizer adaptation, kept separate so backends need not import the loader. */\n\nimport { NerError } from '../core/errors.js'\nimport { extractTokenIds } from './vendor/math.js'\nimport type { SpanTokenizer } from './vendor/span-processor.js'\n\nexport type PretrainedTokenizerLike = (\n text: string,\n options?: { add_special_tokens?: boolean }\n) => { input_ids: { tolist(): (bigint | number)[][] | (bigint | number)[] } }\n\n/**\n * Adapt a transformers.js tokenizer to the span processor's interface.\n *\n * CLS/SEP ids are derived empirically -- encode a throwaway token, read the\n * first and last id -- rather than read from `cls_token_id`. Not every GLiNER\n * repo populates those fields, and a wrong id corrupts every sequence silently\n * instead of failing loudly.\n */\nexport function toSpanTokenizer(tokenizer: PretrainedTokenizerLike): SpanTokenizer {\n const encode = (text: string): number[] =>\n extractTokenIds(tokenizer(text, { add_special_tokens: true }).input_ids.tolist())\n\n const probe = encode('x')\n if (probe.length < 2) {\n throw new NerError(\n 'Tokenizer produced no special tokens; cannot derive CLS/SEP ids',\n 'toSpanTokenizer'\n )\n }\n return { encode, clsTokenId: probe[0] as number, sepTokenId: probe[probe.length - 1] as number }\n}\n","/**\n * GLiNER1 span decoder. Adapted from @lmoe/gliner-onnx (MIT).\n *\n * The model emits logits shaped [batch, seqLen, maxWidth, entityCount]. Every\n * (start word, width, label) triple above threshold becomes a candidate span;\n * greedy non-maximum suppression then keeps the highest-scoring\n * non-overlapping set.\n */\n\nimport { sigmoid } from './math.js'\nimport type { SplitWord } from './splitter.js'\n\nexport interface DecodedSpan {\n text: string\n label: string\n start: number\n end: number\n score: number\n}\n\nexport interface DecodeOptions {\n threshold?: number\n /** Flat NER forbids nesting; set false to allow a span inside another. */\n flatNer?: boolean\n /** Allow the same span to carry more than one label. */\n multiLabel?: boolean\n}\n\n/** Model class ids are 1-based; index 0 is reserved. */\nconst ENTITY_ID_OFFSET = 1\n\nfunction spansOverlap(\n aStart: number,\n aEnd: number,\n bStart: number,\n bEnd: number,\n allowNested: boolean,\n allowMultiLabel: boolean\n): boolean {\n // Identical spans collide unless multi-label output is wanted.\n if (aStart === bStart && aEnd === bEnd) return !allowMultiLabel\n if (aStart > bEnd || bStart > aEnd) return false\n if (allowNested) {\n const nested = (aStart <= bStart && aEnd >= bEnd) || (bStart <= aStart && bEnd >= aEnd)\n if (nested) return false\n }\n return true\n}\n\n/** Keep the highest-scoring spans that do not collide. */\nfunction greedySearch(spans: DecodedSpan[], flatNer: boolean, multiLabel: boolean): DecodedSpan[] {\n const byScore = [...spans].sort((a, b) => b.score - a.score)\n const kept: DecodedSpan[] = []\n for (const span of byScore) {\n const collides = kept.some((other) =>\n spansOverlap(span.start, span.end, other.start, other.end, !flatNer, multiLabel)\n )\n if (!collides) kept.push(span)\n }\n return kept.sort((a, b) => a.start - b.start)\n}\n\nexport function decodeSpans(\n logits: ArrayLike<number>,\n params: {\n batchSize: number\n /** Words per sequence, i.e. the model's span-start axis. */\n inputLength: number\n maxWidth: number\n entityCount: number\n texts: readonly string[]\n batchWords: readonly SplitWord[][]\n idToClass: Record<number, string>\n },\n options: DecodeOptions = {}\n): DecodedSpan[][] {\n const threshold = options.threshold ?? 0.5\n const flatNer = options.flatNer ?? true\n const multiLabel = options.multiLabel ?? false\n\n const { batchSize, inputLength, maxWidth, entityCount } = params\n const batchStride = inputLength * maxWidth * entityCount\n const tokenStride = maxWidth * entityCount\n\n const spans: DecodedSpan[][] = Array.from({ length: batchSize }, () => [])\n\n for (let index = 0; index < logits.length; index++) {\n const score = sigmoid(logits[index] as number)\n if (score < threshold) continue\n\n const batchIdx = Math.floor(index / batchStride)\n const startWord = Math.floor(index / tokenStride) % inputLength\n const endWord = startWord + (Math.floor(index / entityCount) % maxWidth)\n const entityIdx = index % entityCount\n\n const words = params.batchWords[batchIdx]\n if (!words || startWord >= words.length || endWord >= words.length) continue\n\n const start = (words[startWord] as SplitWord)[1]\n const end = (words[endWord] as SplitWord)[2]\n const text = params.texts[batchIdx] ?? ''\n\n ;(spans[batchIdx] as DecodedSpan[]).push({\n text: text.slice(start, end),\n label: params.idToClass[entityIdx + ENTITY_ID_OFFSET] ?? '',\n start,\n end,\n score,\n })\n }\n\n return spans.map((batch) => greedySearch(batch, flatNer, multiLabel))\n}\n","/** Word splitting for GLiNER. Adapted from @lmoe/gliner-onnx (MIT). */\n\n/**\n * Unicode word classes (`\\p{L}\\p{N}` with the `u` flag), deliberately not `\\w`.\n *\n * JavaScript's `\\w` is ASCII-only even under `/u`, so it would shatter accented\n * names -- \"Müller\", \"García\" -- into single-character tokens and wreck\n * multi-word span detection in German, Polish and Spanish text. Python's `\\w`\n * is Unicode-aware, so this restores parity with the reference tokenizer.\n */\nexport const WORD_PATTERN = /[\\p{L}\\p{N}_]+(?:[-_][\\p{L}\\p{N}_]+)*|\\S/gu\n\n/**\n * As above, plus leading branches for URLs, emails and @mentions so they stay\n * whole. Those branches are ASCII on purpose, mirroring the Python pattern.\n */\nexport const RICH_WORD_PATTERN =\n /(?:https?:\\/\\/[^\\s]+|www\\.[^\\s]+)|[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}|@[a-z0-9_]+|[\\p{L}\\p{N}_]+(?:[-_][\\p{L}\\p{N}_]+)*|\\S/giu\n\nexport type SplitWord = [text: string, start: number, end: number]\n\n/**\n * Split text into words with their character offsets.\n *\n * A fresh RegExp per call: the `g` flag makes `lastIndex` stateful, so sharing\n * one instance across calls silently skips matches.\n */\nexport function splitWords(text: string, pattern: RegExp = WORD_PATTERN): SplitWord[] {\n const regex = new RegExp(pattern.source, pattern.flags)\n const out: SplitWord[] = []\n for (;;) {\n const match = regex.exec(text)\n if (match === null) break\n out.push([match[0], match.index, regex.lastIndex])\n }\n return out\n}\n","/**\n * GLiNER1 span-enumeration input builder. Adapted from @lmoe/gliner-onnx (MIT).\n *\n * Builds the prompt `<<ENT>> label1 <<ENT>> label2 <<SEP>> text` and enumerates\n * every candidate span up to `maxWidth` words, which is what the model scores.\n */\n\nimport { type SplitWord, splitWords } from './splitter.js'\n\nconst PAD = 0\n\nexport interface GlinerConfig {\n maxWidth: number\n entToken: string\n sepToken: string\n}\n\n/** Minimal tokenizer surface the processor needs. */\nexport interface SpanTokenizer {\n encode(text: string): number[]\n clsTokenId: number\n sepTokenId: number\n}\n\nexport interface ProcessorBatch {\n inputIds: number[][]\n attentionMasks: number[][]\n wordsMasks: number[][]\n textLengths: number[]\n spanIdxs: number[][][]\n spanMasks: boolean[][]\n /** 1-based entity index -> label, matching the model's class numbering. */\n idToClass: Record<number, string>\n batchWords: SplitWord[][]\n}\n\nfunction pad<T>(arrays: T[][], value: T): T[][] {\n const max = Math.max(...arrays.map((a) => a.length))\n return arrays.map((a) => [...a, ...new Array<T>(max - a.length).fill(value)])\n}\n\nexport class SpanProcessor {\n constructor(\n private readonly config: GlinerConfig,\n private readonly tokenizer: SpanTokenizer\n ) {}\n\n prepare(texts: readonly string[], labels: readonly string[]): ProcessorBatch {\n const batchWords = texts.map((text) => splitWords(text))\n\n const idToClass: Record<number, string> = {}\n for (const [i, label] of labels.entries()) idToClass[i + 1] = label\n\n const inputIds: number[][] = []\n const attentionMasks: number[][] = []\n const wordsMasks: number[][] = []\n const textLengths: number[] = []\n const spanIdxs: number[][][] = []\n const spanMasks: boolean[][] = []\n\n for (const words of batchWords) {\n textLengths.push(words.length)\n\n // Prompt tokens first, then the text's own words.\n const prompt: string[] = []\n for (const label of labels) prompt.push(this.config.entToken, label)\n prompt.push(this.config.sepToken)\n const sequence = [...prompt, ...words.map(([w]) => w)]\n\n const ids: number[] = [this.tokenizer.clsTokenId]\n const attention: number[] = [1]\n // wordsMask marks the FIRST sub-token of each text word with a\n // 1-based word index; prompt tokens and continuation sub-tokens get\n // 0. That is how the model pools sub-tokens back to words.\n const wordsMask: number[] = [PAD]\n let wordCounter = 1\n\n for (const [wordIdx, word] of sequence.entries()) {\n // slice(1, -1) drops the tokenizer's own CLS/SEP around a\n // single word.\n const subTokens = this.tokenizer.encode(word).slice(1, -1)\n for (const [tokenIdx, id] of subTokens.entries()) {\n ids.push(id)\n attention.push(1)\n if (wordIdx < prompt.length) wordsMask.push(PAD)\n else if (tokenIdx === 0) wordsMask.push(wordCounter++)\n else wordsMask.push(PAD)\n }\n }\n ids.push(this.tokenizer.sepTokenId)\n attention.push(1)\n wordsMask.push(PAD)\n\n inputIds.push(ids)\n attentionMasks.push(attention)\n wordsMasks.push(wordsMask)\n\n const spanIdx: number[][] = []\n const spanMask: boolean[] = []\n for (let start = 0; start < words.length; start++) {\n for (let width = 0; width < this.config.maxWidth; width++) {\n const end = Math.min(start + width, words.length - 1)\n spanIdx.push([start, end])\n spanMask.push(end < words.length)\n }\n }\n spanIdxs.push(spanIdx)\n spanMasks.push(spanMask)\n }\n\n const maxSpans = Math.max(...spanIdxs.map((s) => s.length))\n return {\n inputIds: pad(inputIds, PAD),\n attentionMasks: pad(attentionMasks, PAD),\n wordsMasks: pad(wordsMasks, PAD),\n textLengths,\n spanIdxs: spanIdxs.map((s) => [\n ...s,\n ...Array.from({ length: maxSpans - s.length }, () => [PAD, PAD]),\n ]),\n spanMasks: pad(spanMasks, false),\n idToClass,\n batchWords,\n }\n }\n}\n","/**\n * GLiNER1 span-enumeration runtime on onnxruntime-web.\n *\n * One ONNX graph. Inputs:\n * input_ids int64 [batch, tokens]\n * attention_mask int64 [batch, tokens]\n * words_mask int64 [batch, tokens] 1-based word index on first sub-token\n * text_lengths int64 [batch, 1]\n * span_idx int64 [batch, spans, 2]\n * span_mask bool [batch, spans]\n * Output:\n * logits float32 [batch, words, maxWidth, entityCount]\n */\n\nimport { NerError } from '../core/errors.js'\nimport type { ModelFiles } from '../core/model-cache.js'\nimport { createSession } from '../ocr/ort.js'\nimport type { NerBackend, NerBackendLoadOptions } from './backend.js'\nimport { type PretrainedTokenizerLike, toSpanTokenizer } from './tokenizer-types.js'\nimport { type DecodedSpan, decodeSpans } from './vendor/span-decoder.js'\nimport { type GlinerConfig, SpanProcessor } from './vendor/span-processor.js'\n\n/** Fallback when a repo's config omits max_width. */\nconst DEFAULT_MAX_WIDTH = 12\nconst DEFAULT_ENT_TOKEN = '<<ENT>>'\nconst DEFAULT_SEP_TOKEN = '<<SEP>>'\n\nconst CONFIG_CANDIDATES = ['gliner_config.json', 'config.json']\n/** Some repos put the graph at the root rather than under onnx/. */\nconst MODEL_EXTENSIONS = ['.onnx', '.model']\n\nconst OUTPUT_LOGITS = 'logits'\n\nfunction readConfig(files: ModelFiles): GlinerConfig {\n for (const name of CONFIG_CANDIDATES) {\n const raw = files[name]\n if (!raw) continue\n try {\n const parsed = JSON.parse(new TextDecoder().decode(raw)) as Record<string, unknown>\n return {\n maxWidth: Number(parsed.max_width) || DEFAULT_MAX_WIDTH,\n entToken: String(parsed.ent_token ?? DEFAULT_ENT_TOKEN),\n sepToken: String(parsed.sep_token ?? DEFAULT_SEP_TOKEN),\n }\n } catch {\n // Fall through to the next candidate rather than failing outright.\n }\n }\n return {\n maxWidth: DEFAULT_MAX_WIDTH,\n entToken: DEFAULT_ENT_TOKEN,\n sepToken: DEFAULT_SEP_TOKEN,\n }\n}\n\nfunction findModelBytes(files: ModelFiles): ArrayBuffer {\n for (const [path, bytes] of Object.entries(files)) {\n if (MODEL_EXTENSIONS.some((ext) => path.endsWith(ext))) return bytes\n }\n throw new NerError('No .onnx or .model file in the downloaded model', 'Gliner1Backend')\n}\n\nexport class Gliner1Backend implements NerBackend {\n readonly arch = 'gliner1' as const\n\n private session: import('onnxruntime-web').InferenceSession | null = null\n private processor: SpanProcessor | null = null\n private config: GlinerConfig | null = null\n\n async load(files: ModelFiles, options: NerBackendLoadOptions): Promise<void> {\n this.config = readConfig(files)\n this.processor = new SpanProcessor(\n this.config,\n toSpanTokenizer(options.tokenizer as PretrainedTokenizerLike)\n )\n this.session = await createSession(findModelBytes(files), {\n executionProviders: options.executionProviders,\n })\n }\n\n async extract(text: string, labels: readonly string[], threshold: number): Promise<DecodedSpan[]> {\n // Read directly rather than by destructuring: Biome's\n // noUnusedPrivateClassMembers does not see destructured member reads.\n const session = this.session\n const processor = this.processor\n const config = this.config\n if (!session || !processor || !config) {\n throw new NerError('Backend used before load()', 'Gliner1Backend')\n }\n if (labels.length === 0) return []\n\n const batch = processor.prepare([text], labels)\n const words = batch.batchWords[0] ?? []\n if (words.length === 0) return []\n\n const { Tensor } = await import('onnxruntime-web')\n const tokenCount = (batch.inputIds[0] as number[]).length\n const spanCount = (batch.spanIdxs[0] as number[][]).length\n\n const big = (values: number[]) => BigInt64Array.from(values, BigInt)\n\n const feeds: Record<string, import('onnxruntime-web').Tensor> = {\n input_ids: new Tensor('int64', big(batch.inputIds.flat()), [1, tokenCount]),\n attention_mask: new Tensor('int64', big(batch.attentionMasks.flat()), [1, tokenCount]),\n words_mask: new Tensor('int64', big(batch.wordsMasks.flat()), [1, tokenCount]),\n text_lengths: new Tensor('int64', big(batch.textLengths), [1, 1]),\n span_idx: new Tensor('int64', big(batch.spanIdxs.flat(2)), [1, spanCount, 2]),\n span_mask: new Tensor(\n 'bool',\n Uint8Array.from(batch.spanMasks.flat(), (v) => (v ? 1 : 0)),\n [1, spanCount]\n ),\n }\n\n // Only feed inputs this particular export actually declares: token-mode\n // GLiNER variants omit span_idx/span_mask entirely.\n const inputs: Record<string, import('onnxruntime-web').Tensor> = {}\n for (const name of session.inputNames) {\n const tensor = feeds[name]\n if (!tensor) throw new NerError(`Model expects unknown input \"${name}\"`, 'Gliner1Backend')\n inputs[name] = tensor\n }\n\n const outputs = await session.run(inputs)\n const logits = (outputs[OUTPUT_LOGITS] ?? outputs[session.outputNames[0] ?? ''])?.data\n if (!logits) throw new NerError('Model produced no logits', 'Gliner1Backend')\n\n const [spans = []] = decodeSpans(\n logits as Float32Array,\n {\n batchSize: 1,\n inputLength: words.length,\n maxWidth: config.maxWidth,\n entityCount: labels.length,\n texts: [text],\n batchWords: batch.batchWords,\n idToClass: batch.idToClass,\n },\n { threshold, flatNer: true, multiLabel: false }\n )\n return spans\n }\n\n async dispose(): Promise<void> {\n await this.session?.release()\n this.session = null\n this.processor = null\n this.config = null\n }\n}\n","/** GLiNER2 span generation and decoding. Adapted from @lmoe/gliner-onnx (MIT). */\n\nimport { sigmoid } from './math.js'\nimport type { DecodedSpan } from './span-decoder.js'\n\nexport type WordOffset = [start: number, end: number]\n\n/**\n * Every span up to `maxWidth` words.\n *\n * Out-of-range slots are filled with (0, 0) rather than dropped, because the\n * span axis must stay a fixed `seqLen * maxWidth` for the ONNX graph. The\n * decoder skips them by re-checking the word bounds.\n */\nexport function generateSpans(\n seqLen: number,\n maxWidth: number\n): { spanStart: number[]; spanEnd: number[]; spanCount: number } {\n const spanStart: number[] = []\n const spanEnd: number[] = []\n for (let i = 0; i < seqLen; i++) {\n for (let j = 0; j < maxWidth; j++) {\n const inRange = i + j < seqLen\n spanStart.push(inRange ? i : 0)\n spanEnd.push(inRange ? i + j : 0)\n }\n }\n return { spanStart, spanEnd, spanCount: spanStart.length }\n}\n\n/** Sigmoid of the dot product between each span and each label embedding. */\nexport function computeDotProductScores(\n spanRep: Float32Array,\n labelRep: Float32Array,\n spanCount: number,\n labelCount: number,\n hiddenSize: number\n): Float32Array {\n const scores = new Float32Array(spanCount * labelCount)\n for (let s = 0; s < spanCount; s++) {\n const spanOffset = s * hiddenSize\n for (let l = 0; l < labelCount; l++) {\n const labelOffset = l * hiddenSize\n let dot = 0\n for (let h = 0; h < hiddenSize; h++) {\n dot += (spanRep[spanOffset + h] as number) * (labelRep[labelOffset + h] as number)\n }\n scores[s * labelCount + l] = sigmoid(dot)\n }\n }\n return scores\n}\n\nexport interface NerScoreData {\n scores: Float32Array\n wordSpanStart: number[]\n wordSpanEnd: number[]\n spanCount: number\n}\n\n/** Score matrix -> entities, then drop same-label overlaps keeping the best. */\nexport function decodeEntities(\n scoreData: NerScoreData,\n wordCount: number,\n labels: readonly string[],\n wordOffsets: readonly WordOffset[],\n text: string,\n threshold: number\n): DecodedSpan[] {\n const { scores, wordSpanStart, wordSpanEnd, spanCount } = scoreData\n const labelCount = labels.length\n const entities: DecodedSpan[] = []\n\n for (let s = 0; s < spanCount; s++) {\n const startWord = wordSpanStart[s] as number\n const endWord = wordSpanEnd[s] as number\n if (startWord >= wordCount || endWord >= wordCount) continue\n\n for (let l = 0; l < labelCount; l++) {\n const score = scores[s * labelCount + l] as number\n if (score < threshold) continue\n\n const start = (wordOffsets[startWord] as WordOffset)[0]\n const end = (wordOffsets[endWord] as WordOffset)[1]\n entities.push({\n text: text.slice(start, end),\n label: labels[l] as string,\n start,\n end,\n score,\n })\n }\n }\n return deduplicateEntities(entities)\n}\n\n/**\n * Keep the highest-scoring entity among overlapping ones *of the same label*.\n * Different labels may overlap: a person name inside an organization is a\n * legitimate reading, not a conflict.\n */\nfunction deduplicateEntities(entities: readonly DecodedSpan[]): DecodedSpan[] {\n const byScore = [...entities].sort((a, b) => b.score - a.score)\n const kept: DecodedSpan[] = []\n for (const entity of byScore) {\n const overlaps = kept.some(\n (other) => entity.label === other.label && entity.start < other.end && entity.end > other.start\n )\n if (!overlaps) kept.push(entity)\n }\n return kept.sort((a, b) => a.start - b.start)\n}\n","/**\n * GLiNER2 runtime on onnxruntime-web.\n *\n * Three graphs, orchestrated in JS:\n * encoder input_ids, attention_mask [1, seq] int64 -> hidden states\n * span_rep hidden_states [1, seq, hidden] + span_start_idx/span_end_idx\n * [1, spans] int64 -> span representations\n * count_embed label_embeddings [labels, hidden] -> transformed embeddings\n *\n * The `classifier` graph is for text classification only and is not downloaded.\n * Scoring (dot product + sigmoid) happens in JS between span_rep and\n * count_embed.\n *\n * Unlike GLiNER1's single prompt string, GLiNER2 builds a *schema*:\n * ( [P] entities ( [E] label1 [E] label2 ) ) [SEP_TEXT] <text>\n * with each label's position recorded so its embedding can be gathered from\n * the encoder output.\n */\n\nimport { NerError } from '../core/errors.js'\nimport type { ModelFiles } from '../core/model-cache.js'\nimport { createSession } from '../ocr/ort.js'\nimport type { NerBackend, NerBackendLoadOptions } from './backend.js'\nimport type { PretrainedTokenizerLike } from './tokenizer-types.js'\nimport {\n computeDotProductScores,\n decodeEntities,\n generateSpans,\n type WordOffset,\n} from './vendor/gliner2-decoder.js'\nimport { extractTokenIds, gatherRows, sliceRows } from './vendor/math.js'\nimport type { DecodedSpan } from './vendor/span-decoder.js'\nimport { RICH_WORD_PATTERN, splitWords } from './vendor/splitter.js'\n\nconst CONFIG_FILE = 'config.json'\nconst GLINER2_CONFIG_FILE = 'gliner2_config.json'\n\nconst TOKEN_P = '[P]'\nconst TOKEN_E = '[E]'\nconst TOKEN_SEP_TEXT = '[SEP_TEXT]'\nconst SCHEMA_OPEN = '('\nconst SCHEMA_CLOSE = ')'\nconst NER_TASK_NAME = 'entities'\n\ninterface Gliner2Config {\n hiddenSize: number\n maxWidth: number\n specialTokens: Record<string, number>\n}\n\nfunction decodeJson<T>(buffer: ArrayBuffer | undefined, name: string): T {\n if (!buffer) throw new NerError(`Model is missing ${name}`, 'Gliner2Backend')\n return JSON.parse(new TextDecoder().decode(buffer)) as T\n}\n\nexport class Gliner2Backend implements NerBackend {\n readonly arch = 'gliner2' as const\n\n private config: Gliner2Config | null = null\n private encoder: import('onnxruntime-web').InferenceSession | null = null\n private spanRep: import('onnxruntime-web').InferenceSession | null = null\n private countEmbed: import('onnxruntime-web').InferenceSession | null = null\n private tokenize: ((text: string) => number[]) | null = null\n\n async load(files: ModelFiles, options: NerBackendLoadOptions): Promise<void> {\n const transformer = decodeJson<{ hidden_size: number }>(files[CONFIG_FILE], CONFIG_FILE)\n const gliner2 = decodeJson<{ max_width: number; special_tokens: Record<string, number> }>(\n files[GLINER2_CONFIG_FILE],\n GLINER2_CONFIG_FILE\n )\n if (typeof transformer.hidden_size !== 'number') {\n throw new NerError(`${CONFIG_FILE} is missing hidden_size`, 'Gliner2Backend')\n }\n if (typeof gliner2.max_width !== 'number') {\n throw new NerError(`${GLINER2_CONFIG_FILE} is missing max_width`, 'Gliner2Backend')\n }\n\n this.config = {\n hiddenSize: transformer.hidden_size,\n maxWidth: gliner2.max_width,\n specialTokens: gliner2.special_tokens,\n }\n\n const tokenizer = options.tokenizer as PretrainedTokenizerLike\n this.tokenize = (text: string) =>\n extractTokenIds(tokenizer(text, { add_special_tokens: false }).input_ids.tolist())\n\n const graph = (path: string): ArrayBuffer => {\n const bytes = files[path]\n if (!bytes) throw new NerError(`Model is missing ${path}`, 'Gliner2Backend')\n return bytes\n }\n\n // Pinned to WASM by the registry: ORT's WebGPU backend silently drops\n // entities here, because the dynamic span-gather and count_embed ops\n // fall back to CPU mid-graph and the partition boundary corrupts data.\n const providers = options.executionProviders ?? ['wasm']\n const [encoder, spanRep, countEmbed] = await Promise.all([\n createSession(graph('onnx/encoder.onnx'), { executionProviders: providers }),\n createSession(graph('onnx/span_rep.onnx'), { executionProviders: providers }),\n createSession(graph('onnx/count_embed.onnx'), { executionProviders: providers }),\n ])\n this.encoder = encoder\n this.spanRep = spanRep\n this.countEmbed = countEmbed\n }\n\n async extract(text: string, labels: readonly string[], threshold: number): Promise<DecodedSpan[]> {\n const { config, tokenize } = this\n if (!config || !tokenize || !this.encoder || !this.spanRep || !this.countEmbed) {\n throw new NerError('Backend used before load()', 'Gliner2Backend')\n }\n if (text.trim().length === 0 || labels.length === 0) return []\n\n const schema = this.buildSchema(labels)\n const words = this.tokenizeWords(text)\n if (words.wordOffsets.length === 0) return []\n\n const allTokens = [...schema.tokens, ...words.tokens]\n const hidden = await this.encode(allTokens)\n\n const labelEmbeddings = gatherRows(hidden, schema.labelPositions, config.hiddenSize)\n const textTokenCount = allTokens.length - schema.tokens.length\n if (textTokenCount === 0) return []\n\n const textHidden = sliceRows(hidden, schema.tokens.length, textTokenCount, config.hiddenSize)\n\n const { spanStart, spanEnd, spanCount } = generateSpans(words.wordOffsets.length, config.maxWidth)\n // Spans are enumerated over words but scored over tokens, so map each\n // word index to the position of its first sub-token.\n const toToken = (wordIdx: number) => words.firstTokenPositions[wordIdx] ?? 0\n const spanRepresentations = await this.runSpanRep(\n textHidden,\n textTokenCount,\n spanStart.map(toToken),\n spanEnd.map(toToken),\n spanCount\n )\n const transformedLabels = await this.runCountEmbed(labelEmbeddings, labels.length)\n\n const scores = computeDotProductScores(\n spanRepresentations,\n transformedLabels,\n spanCount,\n labels.length,\n config.hiddenSize\n )\n\n return decodeEntities(\n { scores, wordSpanStart: spanStart, wordSpanEnd: spanEnd, spanCount },\n words.wordOffsets.length,\n labels,\n words.wordOffsets,\n text,\n threshold\n )\n }\n\n /** `( [P] entities ( [E] label1 [E] label2 ) ) [SEP_TEXT]` */\n private buildSchema(labels: readonly string[]): { tokens: number[]; labelPositions: number[] } {\n const config = this.config as Gliner2Config\n const tokenize = this.tokenize as (text: string) => number[]\n\n const open = tokenize(SCHEMA_OPEN)\n const close = tokenize(SCHEMA_CLOSE)\n const tokens: number[] = [...open, config.specialTokens[TOKEN_P] as number]\n tokens.push(...tokenize(NER_TASK_NAME), ...open)\n\n const labelPositions: number[] = []\n for (const label of labels) {\n // Record the position of the [E] marker: that token's hidden state\n // is the label's embedding.\n labelPositions.push(tokens.length)\n tokens.push(config.specialTokens[TOKEN_E] as number, ...tokenize(label))\n }\n tokens.push(...close, ...close, config.specialTokens[TOKEN_SEP_TEXT] as number)\n return { tokens, labelPositions }\n }\n\n /**\n * Word-split and tokenize the text.\n *\n * Lower-cased before splitting, matching the reference implementation --\n * offsets stay valid because `toLowerCase` is length-preserving for the\n * scripts these models cover.\n */\n private tokenizeWords(text: string): {\n tokens: number[]\n wordOffsets: WordOffset[]\n firstTokenPositions: number[]\n } {\n const tokenize = this.tokenize as (text: string) => number[]\n const tokens: number[] = []\n const wordOffsets: WordOffset[] = []\n const firstTokenPositions: number[] = []\n\n for (const [word, start, end] of splitWords(text.toLowerCase(), RICH_WORD_PATTERN)) {\n wordOffsets.push([start, end])\n firstTokenPositions.push(tokens.length)\n tokens.push(...tokenize(word))\n }\n return { tokens, wordOffsets, firstTokenPositions }\n }\n\n private async encode(tokens: number[]): Promise<Float32Array> {\n const { Tensor } = await import('onnxruntime-web')\n const session = this.encoder as import('onnxruntime-web').InferenceSession\n const seqLen = tokens.length\n\n const outputs = await session.run({\n input_ids: new Tensor('int64', BigInt64Array.from(tokens, BigInt), [1, seqLen]),\n attention_mask: new Tensor('int64', new BigInt64Array(seqLen).fill(1n), [1, seqLen]),\n })\n return firstOutput(outputs, 'encoder')\n }\n\n private async runSpanRep(\n hidden: Float32Array,\n seqLen: number,\n spanStart: number[],\n spanEnd: number[],\n spanCount: number\n ): Promise<Float32Array> {\n const { Tensor } = await import('onnxruntime-web')\n const session = this.spanRep as import('onnxruntime-web').InferenceSession\n const hiddenSize = (this.config as Gliner2Config).hiddenSize\n\n const outputs = await session.run({\n hidden_states: new Tensor('float32', hidden, [1, seqLen, hiddenSize]),\n span_start_idx: new Tensor('int64', BigInt64Array.from(spanStart, BigInt), [1, spanCount]),\n span_end_idx: new Tensor('int64', BigInt64Array.from(spanEnd, BigInt), [1, spanCount]),\n })\n return firstOutput(outputs, 'span_rep')\n }\n\n private async runCountEmbed(labelEmbeddings: Float32Array, labelCount: number): Promise<Float32Array> {\n const { Tensor } = await import('onnxruntime-web')\n const session = this.countEmbed as import('onnxruntime-web').InferenceSession\n const hiddenSize = (this.config as Gliner2Config).hiddenSize\n\n const outputs = await session.run({\n label_embeddings: new Tensor('float32', labelEmbeddings, [labelCount, hiddenSize]),\n })\n return firstOutput(outputs, 'count_embed')\n }\n\n async dispose(): Promise<void> {\n await Promise.all([this.encoder?.release(), this.spanRep?.release(), this.countEmbed?.release()])\n this.encoder = null\n this.spanRep = null\n this.countEmbed = null\n this.config = null\n this.tokenize = null\n }\n}\n\nfunction firstOutput(\n outputs: import('onnxruntime-web').InferenceSession.OnnxValueMapType,\n graph: string\n): Float32Array {\n const [name] = Object.keys(outputs)\n if (!name) throw new NerError(`Graph ${graph} produced no output`, 'Gliner2Backend')\n return (outputs[name] as { data: Float32Array }).data\n}\n","/**\n * Registry of NER models that can run locally in the browser.\n *\n * Adding a model is a single entry here; the runtimes dispatch on `arch`.\n *\n * The default is deliberately a *public* model, so `npm i` works with no\n * configuration. StabRise's own PII models are private and stay opt-in: pass\n * their id and supply `configure({ auth })` to provide a token.\n */\n\nimport type { ModelFile } from '../core/model-cache.js'\n\nexport type NerArchitecture = 'gliner1' | 'gliner2'\n\nexport interface NerModel {\n /** Short id callers pass to GlinerNer. */\n id: string\n /** Human-readable name including the download size. */\n name: string\n arch: NerArchitecture\n /** Hugging Face repo id. */\n repo: string\n files: ModelFile[]\n /** Labels this model was tuned for. Using others still works, less well. */\n labels: readonly string[]\n languages: readonly string[]\n /** Private repos need `configure({ auth })` to supply a bearer token. */\n private?: boolean\n /** Execution providers this model requires; overrides the global config. */\n executionProviders?: readonly string[]\n}\n\n/** Generic PII labels, matching the pdftools prototype's default set. */\nexport const DEFAULT_PII_LABELS: readonly string[] = Object.freeze([\n 'person',\n 'organization',\n 'location',\n 'email',\n 'phone_number',\n 'url',\n 'id',\n 'account_number',\n 'zip_code',\n 'address',\n 'ip_address',\n 'date',\n 'ssn',\n 'driver_license',\n 'passport',\n 'age',\n 'credit_card',\n 'medical_condition',\n])\n\n/**\n * Label prompts the StabRise GLiNER2 PII model was fine-tuned on.\n *\n * These must match the cloud endpoint's tag list (scaledp-api\n * deidentify/views.py) or scores drop: GLiNER scores a label by its prompt\n * text, so a renamed label is a different label.\n */\nexport const GLINER2_PII_LABELS: readonly string[] = Object.freeze([\n 'date',\n 'person_name',\n 'person_title',\n 'organization',\n 'location',\n 'email',\n 'phone',\n 'id',\n 'account',\n 'zip_code',\n 'address',\n 'ip',\n 'url',\n 'ssn',\n 'driver_license',\n 'passport',\n 'age',\n 'credit_card',\n 'medical_condition',\n 'technology',\n])\n\nexport const NER_MODELS: readonly NerModel[] = Object.freeze([\n {\n id: 'gliner-multi-pii',\n name: 'GLiNER multilingual PII, int8 (~333 MB)',\n arch: 'gliner1',\n repo: 'onnx-community/gliner_multi_pii-v1',\n files: [\n { path: 'gliner_config.json', approxBytes: 800 },\n { path: 'onnx/model_int8.onnx', approxBytes: 349_000_000 },\n ],\n labels: DEFAULT_PII_LABELS,\n languages: ['multi'],\n },\n {\n id: 'gliner-small',\n name: 'GLiNER small English, int8 (~183 MB)',\n arch: 'gliner1',\n repo: 'onnx-community/gliner_small-v2.1',\n files: [\n { path: 'gliner_config.json', approxBytes: 731 },\n { path: 'onnx/model_quantized.onnx', approxBytes: 183_403_734 },\n ],\n labels: DEFAULT_PII_LABELS,\n languages: ['en'],\n },\n {\n // Same GLiNER1 span-enumeration architecture and ONNX I/O as the models\n // above, on an LFM2.5-350M backbone converted to bidirectional. Its ONNX\n // file sits at the repo root, not under onnx/.\n id: 'stabrise-pii-multi',\n name: 'StabRise PII multilingual, int8 (~404 MB)',\n arch: 'gliner1',\n repo: 'StabRise/pii-detection-en-fr-ge-it-es',\n files: [\n { path: 'config.json', approxBytes: 3_417 },\n { path: 'model_int8.model', approxBytes: 403_923_207 },\n ],\n labels: DEFAULT_PII_LABELS,\n languages: ['en', 'fr', 'de', 'it', 'es'],\n private: true,\n },\n {\n // Opt-in only. The published weights are fp32 and total roughly 1.2 GB,\n // which is not a reasonable browser download; there is no int8 or q4\n // variant on the Hub. Available for desktop-wrapped or kiosk builds.\n //\n // Pinned to WASM: onnxruntime-web's WebGPU backend silently drops\n // entities on this architecture. GLiNER2's dynamic span-gather and\n // count_embed ops fall back to CPU mid-graph, and the resulting\n // CPU/WebGPU partition boundary corrupts results rather than erroring.\n id: 'stabrise-pii-multi-g2',\n name: 'StabRise PII multilingual GLiNER2, fp32 (~1.2 GB)',\n arch: 'gliner2',\n repo: 'StabRise/pii-multi-g2-v1-onnx',\n files: [\n { path: 'config.json', approxBytes: 48 },\n { path: 'gliner2_config.json', approxBytes: 691 },\n { path: 'onnx/encoder.onnx', approxBytes: 1_111_055_946 },\n { path: 'onnx/span_rep.onnx', approxBytes: 66_111_424 },\n { path: 'onnx/count_embed.onnx', approxBytes: 42_506_885 },\n ],\n labels: GLINER2_PII_LABELS,\n languages: ['en', 'de', 'pl', 'es'],\n private: true,\n executionProviders: ['wasm'],\n },\n])\n\n/** Public, zero-configuration default. */\nexport const DEFAULT_NER_MODEL_ID = 'gliner-multi-pii'\n\nexport function getNerModel(id: string): NerModel | undefined {\n return NER_MODELS.find((m) => m.id === id)\n}\n\n/** Total download size in bytes, for a progress estimate before fetching. */\nexport function modelSizeBytes(model: NerModel): number {\n return model.files.reduce((sum, f) => sum + (f.approxBytes ?? 0), 0)\n}\n","/**\n * Tokenizer loading and adaptation for the GLiNER runtimes.\n *\n * `@huggingface/transformers` provides the tokenizer only; no model runs\n * through it. Its remote host is configurable so gated repos can be proxied\n * through the consuming application's own origin.\n */\n\nimport { getConfig } from '../core/config.js'\nimport { extractTokenIds } from './vendor/math.js'\n\ntype Transformers = typeof import('@huggingface/transformers')\ntype PretrainedTokenizer = Awaited<ReturnType<Transformers['AutoTokenizer']['from_pretrained']>>\n\nlet modulePromise: Promise<Transformers> | null = null\n\nasync function loadTransformers(): Promise<Transformers> {\n if (modulePromise) return modulePromise\n\n modulePromise = (async () => {\n let mod: Transformers\n try {\n mod = await import('@huggingface/transformers')\n } catch (cause) {\n throw new Error(\n '@huggingface/transformers is required for NER tokenization. Install it: npm i @huggingface/transformers',\n { cause }\n )\n }\n\n const { hf } = getConfig()\n // Tokenizers come over HTTP; nothing is resolved from the filesystem.\n mod.env.allowLocalModels = false\n if (hf.remoteHost) mod.env.remoteHost = hf.remoteHost\n if (hf.remotePathTemplate) mod.env.remotePathTemplate = hf.remotePathTemplate\n return mod\n })()\n\n return modulePromise\n}\n\n/** Reset the cached module. Tests only. */\nexport function resetTransformers(): void {\n modulePromise = null\n}\n\nconst tokenizers = new Map<string, Promise<PretrainedTokenizer>>()\n\nexport async function loadTokenizer(repo: string): Promise<PretrainedTokenizer> {\n const existing = tokenizers.get(repo)\n if (existing) return existing\n\n const promise = (async () => {\n const { AutoTokenizer } = await loadTransformers()\n return AutoTokenizer.from_pretrained(repo)\n })()\n\n promise.catch(() => tokenizers.delete(repo))\n tokenizers.set(repo, promise)\n return promise\n}\n\n/** Adapt to the callable form the GLiNER2 runtime expects. */\nexport function toCallableTokenizer(tokenizer: PretrainedTokenizer): (text: string) => number[] {\n return (text: string) => {\n const encoded = tokenizer(text, { add_special_tokens: false }) as {\n input_ids: { tolist(): (bigint | number)[][] | (bigint | number)[] }\n }\n return extractTokenIds(encoded.input_ids.tolist())\n }\n}\n","/**\n * Named-entity recognition over a Document, mirroring ScaleDP's `Ner` stage.\n *\n * Chunks long text, runs a GLiNER backend, and maps the resulting character\n * offsets back onto the OCR boxes so every entity carries its position on the\n * page.\n */\n\nimport { getConfig } from '../core/config.js'\nimport { NerError } from '../core/errors.js'\nimport { ensureModelFiles } from '../core/model-cache.js'\nimport { BASE_STAGE_DEFAULTS, type BaseStageParams, resolveParams } from '../core/params.js'\nimport { type Row, Stage } from '../core/pipeline.js'\nimport type { Box } from '../schemas/box.js'\nimport type { Document } from '../schemas/document.js'\nimport { createNerOutput, type Entity, type NerOutput } from '../schemas/entity.js'\nimport type { NerBackend } from './backend.js'\nimport {\n chunkText,\n DEFAULT_CHUNK_LENGTH,\n DEFAULT_CHUNK_STRIDE,\n dedupeSpans,\n normaliseCasing,\n rebaseSpan,\n} from './chunking.js'\nimport { Gliner1Backend } from './gliner1-backend.js'\nimport { Gliner2Backend } from './gliner2-backend.js'\nimport { DEFAULT_NER_MODEL_ID, DEFAULT_PII_LABELS, getNerModel } from './registry.js'\nimport { loadTokenizer } from './tokenizer.js'\nimport type { DecodedSpan } from './vendor/span-decoder.js'\n\nexport interface GlinerNerParams extends BaseStageParams {\n /** Registry id, e.g. 'gliner-multi-pii'. */\n model: string\n /** Entity types to look for. GLiNER scores a label by its prompt text. */\n labels: readonly string[]\n /** Minimum score an entity must reach (0-1). */\n threshold: number\n /** Only keep these entity groups; empty keeps everything. */\n whiteList: readonly string[]\n chunkLength: number\n chunkStride: number\n /**\n * Title-case runs of capitals before inference. GLiNER1 models are cased\n * and scanned documents are often set in all caps.\n */\n normaliseCasing: boolean\n}\n\nexport const GLINER_NER_DEFAULTS: GlinerNerParams = Object.freeze({\n ...BASE_STAGE_DEFAULTS,\n inputCol: 'text',\n outputCol: 'ner',\n keepInputData: true,\n model: DEFAULT_NER_MODEL_ID,\n labels: DEFAULT_PII_LABELS,\n threshold: 0.5,\n whiteList: [] as readonly string[],\n chunkLength: DEFAULT_CHUNK_LENGTH,\n chunkStride: DEFAULT_CHUNK_STRIDE,\n normaliseCasing: true,\n})\n\n/**\n * Map each character of the joined document text to the box it came from.\n *\n * Built from the *actual* text the OCR stage produced rather than assuming one\n * separator per box. Python derives the mapping from `len(box.text) + 1`, which\n * silently drifts whenever `keepFormatting` inserted several spaces or a\n * newline, shifting every entity's boxes after the first wide gap.\n */\nexport function buildCharToBoxMap(text: string, boxes: readonly Box[]): Int32Array {\n const mapping = new Int32Array(text.length).fill(-1)\n let cursor = 0\n\n for (const [index, box] of boxes.entries()) {\n if (box.text.length === 0) continue\n const found = text.indexOf(box.text, cursor)\n if (found === -1) continue\n mapping.fill(index, found, found + box.text.length)\n cursor = found + box.text.length\n }\n return mapping\n}\n\n/** Boxes a character range touches, in document order and without repeats. */\nexport function boxesForRange(mapping: Int32Array, boxes: readonly Box[], start: number, end: number): Box[] {\n const seen = new Set<number>()\n const out: Box[] = []\n for (let i = Math.max(0, start); i < Math.min(end, mapping.length); i++) {\n const index = mapping[i] as number\n if (index < 0 || seen.has(index)) continue\n seen.add(index)\n const box = boxes[index]\n if (box) out.push(box)\n }\n return out\n}\n\nexport class GlinerNer extends Stage<GlinerNerParams> {\n readonly name = 'GlinerNer'\n\n private backend: NerBackend | null = null\n private loading: Promise<NerBackend> | null = null\n\n constructor(options: Partial<GlinerNerParams> = {}) {\n super(\n resolveParams(GLINER_NER_DEFAULTS, options, {\n threshold: (value) => {\n if (!(value >= 0 && value <= 1)) {\n throw new RangeError(`threshold must be between 0 and 1, received ${value}`)\n }\n },\n model: (value) => {\n if (!getNerModel(value)) {\n throw new RangeError(`Unknown NER model \"${value}\". See NER_MODELS for valid ids.`)\n }\n },\n })\n )\n }\n\n override async init(): Promise<void> {\n await this.getBackend()\n }\n\n private getBackend(): Promise<NerBackend> {\n if (this.backend) return Promise.resolve(this.backend)\n if (this.loading) return this.loading\n\n this.loading = (async () => {\n const model = getNerModel(this.params.model)\n if (!model) throw new NerError(`Unknown model ${this.params.model}`, this.name)\n if (model.private && !getConfig().auth) {\n throw new NerError(\n `Model ${model.id} lives in a private repo. Supply a token via configure({ auth }).`,\n this.name\n )\n }\n\n const [files, tokenizer] = await Promise.all([\n ensureModelFiles({ repo: model.repo, files: model.files }),\n loadTokenizer(model.repo),\n ])\n\n const backend: NerBackend = model.arch === 'gliner2' ? new Gliner2Backend() : new Gliner1Backend()\n await backend.load(files, {\n tokenizer,\n executionProviders: model.executionProviders ?? getConfig().executionProviders,\n })\n this.backend = backend\n return backend\n })()\n\n this.loading.catch(() => {\n this.loading = null\n })\n return this.loading\n }\n\n protected async apply(input: unknown, row: Row): Promise<NerOutput> {\n const document = input as Document | undefined\n if (!document || typeof document.text !== 'string') {\n throw new NerError('Expected a Document with text', this.name)\n }\n if (document.exception) {\n throw new NerError(`Upstream stage failed: ${document.exception}`, this.name)\n }\n\n const entities = await this.extract(document)\n return createNerOutput({\n path: String(row[this.params.pathCol] ?? document.path ?? 'memory'),\n entities,\n json: JSON.stringify(entities),\n })\n }\n\n /** Run NER over a document and attach boxes to every entity found. */\n async extract(document: Document): Promise<Entity[]> {\n const { labels, threshold, whiteList, chunkLength, chunkStride } = this.params\n if (document.text.trim().length === 0 || labels.length === 0) return []\n\n const backend = await this.getBackend()\n // Casing is normalised length-preservingly, so offsets stay valid\n // against the ORIGINAL text -- entity strings are re-sliced from it.\n const source = this.params.normaliseCasing ? normaliseCasing(document.text) : document.text\n\n const spans: DecodedSpan[] = []\n for (const chunk of chunkText(source, chunkLength, chunkStride)) {\n const found = await backend.extract(chunk.text, labels, threshold)\n for (const span of found) spans.push(rebaseSpan(span, chunk.offset))\n }\n\n const mapping = buildCharToBoxMap(document.text, document.bboxes)\n const allowed = new Set(whiteList)\n\n return dedupeSpans(spans)\n .filter((span) => allowed.size === 0 || allowed.has(span.label))\n .map((span) => ({\n entity_group: span.label,\n score: span.score,\n // Re-slice from the original text so the reported word keeps its\n // real casing, not the normalised form the model saw.\n word: document.text.slice(span.start, span.end),\n start: span.start,\n end: span.end,\n boxes: boxesForRange(mapping, document.bboxes, span.start, span.end),\n }))\n }\n\n protected onError(message: string, row: Row): NerOutput {\n return createNerOutput({\n path: String(row[this.params.pathCol] ?? 'memory'),\n exception: message,\n })\n }\n\n override async dispose(): Promise<void> {\n await this.backend?.dispose()\n this.backend = null\n this.loading = null\n }\n}\n"],"mappings":"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"}
@@ -246,7 +246,23 @@ interface SessionOptions {
246
246
  path: string;
247
247
  data: ArrayBuffer | Uint8Array;
248
248
  }[];
249
+ /**
250
+ * Retry on WASM when the preferred providers cannot run the graph.
251
+ *
252
+ * Off by default, so a misconfigured provider still fails loudly. Turn it on
253
+ * for a model known to be picky: WebGPU rewrites convolutions into its
254
+ * `com.ms.internal.nhwc` domain and a graph it has no kernel for dies at
255
+ * session creation, not at inference.
256
+ */
257
+ fallbackToWasm?: boolean;
249
258
  }
259
+ /**
260
+ * The provider list to retry with, or `null` when there is nothing safer left.
261
+ *
262
+ * Mirrors ppu-paddle-ocr's own fallback, so a stage building its own session
263
+ * behaves the same as one going through `PaddleOcrService`.
264
+ */
265
+ declare function wasmFallbackProviders(providers: readonly string[]): string[] | null;
250
266
  /** Create an inference session from model bytes. */
251
267
  declare function createSession(model: ArrayBuffer | Uint8Array, options?: SessionOptions): Promise<import('onnxruntime-web').InferenceSession>;
252
268
  /** True when the browser exposes a usable WebGPU adapter. */
@@ -268,7 +284,7 @@ interface PaddleOcrParams extends BaseStageParams {
268
284
  scoreThreshold: number;
269
285
  }
270
286
  interface PaddleTextDetectorParams extends PaddleOcrParams {}
271
- declare const PADDLE_DETECTOR_DEFAULTS: PaddleTextDetectorParams;
287
+ declare const PADDLE_TEXT_DETECTOR_DEFAULTS: PaddleTextDetectorParams;
272
288
  /** Text detection only: image -> word boxes, no recognition. */
273
289
  declare class PaddleTextDetector extends Stage<PaddleTextDetectorParams> {
274
290
  readonly name = "PaddleTextDetector";
@@ -293,7 +309,7 @@ interface PaddleTextRecognizerParams extends PaddleOcrParams {
293
309
  /** Line-grouping tolerance in pixels; 0 derives it from character height. */
294
310
  lineTolerance: number;
295
311
  }
296
- declare const PADDLE_RECOGNIZER_DEFAULTS: PaddleTextRecognizerParams;
312
+ declare const PADDLE_TEXT_RECOGNIZER_DEFAULTS: PaddleTextRecognizerParams;
297
313
  /** Full OCR: image -> Document with text and word-level boxes. */
298
314
  declare class PaddleTextRecognizer extends Stage<PaddleTextRecognizerParams> {
299
315
  readonly name = "PaddleTextRecognizer";
@@ -303,6 +319,55 @@ declare class PaddleTextRecognizer extends Stage<PaddleTextRecognizerParams> {
303
319
  protected onError(message: string, row: Row): Document;
304
320
  }
305
321
  //#endregion
322
+ //#region src/ocr/paddle-recognizer.d.ts
323
+ interface PaddleRecognizerParams extends BaseStageParams {
324
+ /** [imageColumn, boxColumn]. */
325
+ inputCols: string[];
326
+ /** Language/script preset. See PADDLE_OCR_PRESETS. */
327
+ preset: string;
328
+ /** Resize the page by this factor before cropping. */
329
+ scaleFactor: number;
330
+ /** Grow each box before cropping. ScaleDP hardcodes 5. */
331
+ padding: number;
332
+ /** Drop regions below this confidence (0-1). */
333
+ scoreThreshold: number;
334
+ /** Rebuild the original layout with spaces and blank lines. */
335
+ keepFormatting: boolean;
336
+ /** Line-grouping tolerance in pixels; 0 derives it from character height. */
337
+ lineTolerance: number;
338
+ /**
339
+ * Classify each crop 0/180 degrees and turn the inverted ones.
340
+ *
341
+ * Off by default, unlike `TesseractRecognizer`. Paddle already turns a crop
342
+ * that is markedly taller than wide, so only the 180 degree flip is missing
343
+ * -- and catching it costs a separate ~9 MB model an ordinary page never
344
+ * needs. Turn it on for scans that come in upside down.
345
+ */
346
+ detectLineOrientation: boolean;
347
+ /** Recognize only boxes that are rotated or came back inverted. */
348
+ onlyRotated: boolean;
349
+ oriModel: string;
350
+ /**
351
+ * Recover inter-word spaces the greedy CTC decode drops. Helps Latin text
352
+ * where the model collapses word gaps; can add spurious ones in dense
353
+ * symbol runs.
354
+ */
355
+ spaceRecovery: boolean;
356
+ /** Crops per batched inference. 1 disables batching. */
357
+ recBatchSize: number;
358
+ }
359
+ declare const PADDLE_RECOGNIZER_DEFAULTS: PaddleRecognizerParams;
360
+ declare class PaddleRecognizer extends Stage<PaddleRecognizerParams> {
361
+ readonly name = "PaddleRecognizer";
362
+ private orientation;
363
+ private recognition;
364
+ constructor(options?: Partial<PaddleRecognizerParams>);
365
+ init(): Promise<void>;
366
+ protected apply(_input: unknown, row: Row, ctx: StageContext): Promise<Document>;
367
+ protected onError(message: string, row: Row): Document;
368
+ dispose(): Promise<void>;
369
+ }
370
+ //#endregion
306
371
  //#region src/ocr/paddle-service.d.ts
307
372
  /**
308
373
  * PaddleOcrService lifecycle: model fetching, caching and per-preset reuse.
@@ -311,15 +376,44 @@ declare class PaddleTextRecognizer extends Stage<PaddleTextRecognizerParams> {
311
376
  * ArrayBuffers, rather than letting ppu-paddle-ocr fetch them itself. Its
312
377
  * browser build re-downloads ~6 MB on every page load, relying only on the HTTP
313
378
  * cache; routing through IndexedDB makes a repeat visit instant and offline-safe.
379
+ *
380
+ * Two entry points, because the two halves of a preset are separable:
381
+ * `getPaddleService` builds the full detect-then-read service, while
382
+ * `getPaddleRecognizer` loads only the recognition model and its dictionary --
383
+ * what `PaddleRecognizer` needs when some other detector already found the
384
+ * boxes. Both key their cache on the same repo, so a pipeline using both pays
385
+ * for each file once.
314
386
  */
315
387
  type PpuWeb = typeof import('ppu-paddle-ocr/web');
316
388
  type PaddleOcrService = InstanceType<PpuWeb['PaddleOcrService']>;
389
+ /**
390
+ * ppu's recognition half, bound to a session: `run(canvas, boxes)` reads the
391
+ * regions it is handed and does no detection of its own.
392
+ */
393
+ type PaddleRecognitionService = InstanceType<PpuWeb['RecognitionService']>;
394
+ /** Recognition tuning a stage may vary without paying for a second session. */
395
+ interface PaddleRecognizerOptions {
396
+ /** Crops per batched inference. 1 disables batching. */
397
+ recBatchSize?: number;
398
+ /** Recover inter-word spaces the greedy CTC decode drops. */
399
+ spaceRecovery?: boolean;
400
+ }
317
401
  declare function getPaddleService(preset?: string): Promise<PaddleOcrService>;
402
+ /**
403
+ * Recognition only: read the regions you hand it, no detection.
404
+ *
405
+ * `minimumConfidence` is pinned to 0 and `maxCropSourceSideLength` is set past
406
+ * any canvas we pass. ppu would otherwise silently drop low-scoring results --
407
+ * breaking the caller's box-to-result mapping, since `run` also sorts what it
408
+ * returns into reading order -- and downscale a tall batch of stacked crops.
409
+ * Filtering is the calling stage's job, where the threshold is a parameter.
410
+ */
411
+ declare function getPaddleRecognizer(preset?: string, options?: PaddleRecognizerOptions): Promise<PaddleRecognitionService>;
318
412
  declare function isPresetCached(preset: string): Promise<boolean>;
319
413
  /** Pre-warm a preset so the first OCR call does not pay the download. */
320
414
  declare function loadPreset(preset: string): Promise<void>;
321
415
  declare function removePreset(preset: string): Promise<void>;
322
- /** Tear down every cached service. */
416
+ /** Tear down every cached service, recognition-only sessions included. */
323
417
  declare function disposePaddleServices(): Promise<void>;
324
418
  //#endregion
325
419
  //#region src/ocr/presets.d.ts
@@ -344,6 +438,8 @@ interface OcrPreset {
344
438
  declare const PADDLE_OCR_PRESETS: readonly OcrPreset[];
345
439
  declare const DEFAULT_OCR_PRESET = "v6-small";
346
440
  declare function isKnownPreset(value: string): boolean;
441
+ /** Shared by every Paddle stage, so an unknown preset fails at construction. */
442
+ declare function validatePreset(value: string): void;
347
443
  /** Presets able to read a script name as reported by OSD script detection. */
348
444
  declare function presetsForScript(script: string): OcrPreset[];
349
445
  //#endregion
@@ -436,5 +532,5 @@ declare class TesseractRecognizer extends Stage<TesseractRecognizerParams> {
436
532
  dispose(): Promise<void>;
437
533
  }
438
534
  //#endregion
439
- export { DBNET_DETECTOR_DEFAULTS, DBNET_INPUT_SIZE, DB_POSTPROCESS_DEFAULTS, DEFAULT_DBNET_MODEL, DEFAULT_DETECTOR_ID, DEFAULT_OCR_PRESET, DEFAULT_ORIENTATION_MODEL, DEFAULT_TESSDATA_URL, DETECTOR_MODELS, type DbPostProcessOptions, DbnetOnnxDetector, type DbnetOnnxDetectorParams, type DetectedQuad, type DetectedScript, type DetectorKind, type DetectorModel, LINE_ORIENTATION_DEFAULTS, type LineOrientation, LineOrientationClassifier, LineOrientationDetector, type LineOrientationDetectorParams, ORIENTATION_INPUT, type OcrPreset, PADDLE_DETECTOR_DEFAULTS, PADDLE_OCR_PRESETS, PADDLE_RECOGNIZER_DEFAULTS, type PaddleOcrParams, PaddleTextDetector, type PaddleTextDetectorParams, PaddleTextRecognizer, type PaddleTextRecognizerParams, type ProbabilityMap, type RecognitionStrategy, type SessionOptions, TESSERACT_OCR_DEFAULTS, TESSERACT_RECOGNIZER_DEFAULTS, TesseractOcr, type TesseractOcrParams, TesseractRecognizer, type TesseractRecognizerParams, boxScore, createSession, detectScript, disposePaddleServices, disposeScriptDetection, disposeTesseract, findComponentBoundaries, getDetectorModel, getPaddleService, isCrossOriginIsolated, isKnownPreset, isPresetCached, isWebGpuAvailable, loadOrt, loadPreset, miniBox, orderPointsClockwise, presetsForScript, quadsFromProbabilityMap, removePreset, resetOrt, suggestPresets, unclipRect };
535
+ export { DBNET_DETECTOR_DEFAULTS, DBNET_INPUT_SIZE, DB_POSTPROCESS_DEFAULTS, DEFAULT_DBNET_MODEL, DEFAULT_DETECTOR_ID, DEFAULT_OCR_PRESET, DEFAULT_ORIENTATION_MODEL, DEFAULT_TESSDATA_URL, DETECTOR_MODELS, type DbPostProcessOptions, DbnetOnnxDetector, type DbnetOnnxDetectorParams, type DetectedQuad, type DetectedScript, type DetectorKind, type DetectorModel, LINE_ORIENTATION_DEFAULTS, type LineOrientation, LineOrientationClassifier, LineOrientationDetector, type LineOrientationDetectorParams, ORIENTATION_INPUT, type OcrPreset, PADDLE_OCR_PRESETS, PADDLE_RECOGNIZER_DEFAULTS, PADDLE_TEXT_DETECTOR_DEFAULTS, PADDLE_TEXT_RECOGNIZER_DEFAULTS, type PaddleOcrParams, type PaddleRecognitionService, PaddleRecognizer, type PaddleRecognizerOptions, type PaddleRecognizerParams, PaddleTextDetector, type PaddleTextDetectorParams, PaddleTextRecognizer, type PaddleTextRecognizerParams, type ProbabilityMap, type RecognitionStrategy, type SessionOptions, TESSERACT_OCR_DEFAULTS, TESSERACT_RECOGNIZER_DEFAULTS, TesseractOcr, type TesseractOcrParams, TesseractRecognizer, type TesseractRecognizerParams, boxScore, createSession, detectScript, disposePaddleServices, disposeScriptDetection, disposeTesseract, findComponentBoundaries, getDetectorModel, getPaddleRecognizer, getPaddleService, isCrossOriginIsolated, isKnownPreset, isPresetCached, isWebGpuAvailable, loadOrt, loadPreset, miniBox, orderPointsClockwise, presetsForScript, quadsFromProbabilityMap, removePreset, resetOrt, suggestPresets, unclipRect, validatePreset, wasmFallbackProviders };
440
536
  //# sourceMappingURL=index.d.ts.map
package/dist/ocr/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { A as getDetectorModel, B as quadsFromProbabilityMap, C as LINE_ORIENTATION_DEFAULTS, D as ORIENTATION_INPUT, E as LineOrientationClassifier, F as DB_POSTPROCESS_DEFAULTS, I as boxScore, L as findComponentBoundaries, M as DBNET_INPUT_SIZE, N as DEFAULT_DBNET_MODEL, O as DEFAULT_DETECTOR_ID, P as DbnetOnnxDetector, R as miniBox, S as presetsForScript, T as DEFAULT_ORIENTATION_MODEL, V as unclipRect, _ as loadPreset, a as TesseractOcr, b as PADDLE_OCR_PRESETS, c as disposeScriptDetection, d as PADDLE_RECOGNIZER_DEFAULTS, f as PaddleTextDetector, g as isPresetCached, h as getPaddleService, i as TESSERACT_OCR_DEFAULTS, j as DBNET_DETECTOR_DEFAULTS, k as DETECTOR_MODELS, l as suggestPresets, m as disposePaddleServices, n as TesseractRecognizer, o as disposeTesseract, p as PaddleTextRecognizer, r as DEFAULT_TESSDATA_URL, s as detectScript, t as TESSERACT_RECOGNIZER_DEFAULTS, u as PADDLE_DETECTOR_DEFAULTS, v as removePreset, w as LineOrientationDetector, x as isKnownPreset, y as DEFAULT_OCR_PRESET, z as orderPointsClockwise } from "../ocr-OHX2WM3e.js";
2
- import { a as resetOrt, i as loadOrt, n as isCrossOriginIsolated, r as isWebGpuAvailable, t as createSession } from "../ort-CXDoPrtw.js";
3
- export { DBNET_DETECTOR_DEFAULTS, DBNET_INPUT_SIZE, DB_POSTPROCESS_DEFAULTS, DEFAULT_DBNET_MODEL, DEFAULT_DETECTOR_ID, DEFAULT_OCR_PRESET, DEFAULT_ORIENTATION_MODEL, DEFAULT_TESSDATA_URL, DETECTOR_MODELS, DbnetOnnxDetector, LINE_ORIENTATION_DEFAULTS, LineOrientationClassifier, LineOrientationDetector, ORIENTATION_INPUT, PADDLE_DETECTOR_DEFAULTS, PADDLE_OCR_PRESETS, PADDLE_RECOGNIZER_DEFAULTS, PaddleTextDetector, PaddleTextRecognizer, TESSERACT_OCR_DEFAULTS, TESSERACT_RECOGNIZER_DEFAULTS, TesseractOcr, TesseractRecognizer, boxScore, createSession, detectScript, disposePaddleServices, disposeScriptDetection, disposeTesseract, findComponentBoundaries, getDetectorModel, getPaddleService, isCrossOriginIsolated, isKnownPreset, isPresetCached, isWebGpuAvailable, loadOrt, loadPreset, miniBox, orderPointsClockwise, presetsForScript, quadsFromProbabilityMap, removePreset, resetOrt, suggestPresets, unclipRect };
1
+ import { A as LineOrientationClassifier, B as boxScore, C as PADDLE_OCR_PRESETS, D as LINE_ORIENTATION_DEFAULTS, E as validatePreset, F as DBNET_DETECTOR_DEFAULTS, G as unclipRect, H as miniBox, I as DBNET_INPUT_SIZE, L as DEFAULT_DBNET_MODEL, M as DEFAULT_DETECTOR_ID, N as DETECTOR_MODELS, O as LineOrientationDetector, P as getDetectorModel, R as DbnetOnnxDetector, S as DEFAULT_OCR_PRESET, T as presetsForScript, U as orderPointsClockwise, V as findComponentBoundaries, W as quadsFromProbabilityMap, _ as getPaddleRecognizer, a as TesseractOcr, b as loadPreset, c as disposeScriptDetection, d as PaddleRecognizer, f as PADDLE_TEXT_DETECTOR_DEFAULTS, g as disposePaddleServices, h as PaddleTextRecognizer, i as TESSERACT_OCR_DEFAULTS, j as ORIENTATION_INPUT, k as DEFAULT_ORIENTATION_MODEL, l as suggestPresets, m as PaddleTextDetector, n as TesseractRecognizer, o as disposeTesseract, p as PADDLE_TEXT_RECOGNIZER_DEFAULTS, r as DEFAULT_TESSDATA_URL, s as detectScript, t as TESSERACT_RECOGNIZER_DEFAULTS, u as PADDLE_RECOGNIZER_DEFAULTS, v as getPaddleService, w as isKnownPreset, x as removePreset, y as isPresetCached, z as DB_POSTPROCESS_DEFAULTS } from "../ocr-Dkkbk0Wl.js";
2
+ import { a as resetOrt, i as loadOrt, n as isCrossOriginIsolated, o as wasmFallbackProviders, r as isWebGpuAvailable, t as createSession } from "../ort-DZEG14nY.js";
3
+ export { DBNET_DETECTOR_DEFAULTS, DBNET_INPUT_SIZE, DB_POSTPROCESS_DEFAULTS, DEFAULT_DBNET_MODEL, DEFAULT_DETECTOR_ID, DEFAULT_OCR_PRESET, DEFAULT_ORIENTATION_MODEL, DEFAULT_TESSDATA_URL, DETECTOR_MODELS, DbnetOnnxDetector, LINE_ORIENTATION_DEFAULTS, LineOrientationClassifier, LineOrientationDetector, ORIENTATION_INPUT, PADDLE_OCR_PRESETS, PADDLE_RECOGNIZER_DEFAULTS, PADDLE_TEXT_DETECTOR_DEFAULTS, PADDLE_TEXT_RECOGNIZER_DEFAULTS, PaddleRecognizer, PaddleTextDetector, PaddleTextRecognizer, TESSERACT_OCR_DEFAULTS, TESSERACT_RECOGNIZER_DEFAULTS, TesseractOcr, TesseractRecognizer, boxScore, createSession, detectScript, disposePaddleServices, disposeScriptDetection, disposeTesseract, findComponentBoundaries, getDetectorModel, getPaddleRecognizer, getPaddleService, isCrossOriginIsolated, isKnownPreset, isPresetCached, isWebGpuAvailable, loadOrt, loadPreset, miniBox, orderPointsClockwise, presetsForScript, quadsFromProbabilityMap, removePreset, resetOrt, suggestPresets, unclipRect, validatePreset, wasmFallbackProviders };