web-sdk-pp-detection 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,2355 @@
1
+ // src/errors.ts
2
+ var PPDetectionError = class extends Error {
3
+ constructor(code, message, details = {}, options) {
4
+ super(message, options);
5
+ this.code = code;
6
+ this.details = details;
7
+ this.name = "PPDetectionError";
8
+ }
9
+ code;
10
+ details;
11
+ };
12
+
13
+ // src/input/decode-image.ts
14
+ function nowDefault() {
15
+ return globalThis.performance?.now() ?? Date.now();
16
+ }
17
+ function throwIfAborted(signal) {
18
+ if (signal?.aborted) {
19
+ throw new PPDetectionError("ABORTED", "\u56FE\u7247\u89E3\u7801\u5DF2\u53D6\u6D88", { reason: signal.reason });
20
+ }
21
+ }
22
+ function isImageDataLike(value) {
23
+ return typeof value === "object" && value !== null && "width" in value && "height" in value && ("data" in value && value.data instanceof Uint8ClampedArray || "rgba" in value && value.rgba instanceof Uint8ClampedArray);
24
+ }
25
+ function validateRaster(raster) {
26
+ if (!Number.isInteger(raster.width) || !Number.isInteger(raster.height) || raster.width <= 0 || raster.height <= 0 || raster.rgba.length !== raster.width * raster.height * 4) {
27
+ throw new PPDetectionError("INVALID_INPUT", "\u56FE\u7247\u5C3A\u5BF8\u6216 RGBA \u6570\u636E\u65E0\u6548", {
28
+ width: raster.width,
29
+ height: raster.height,
30
+ rgbaLength: raster.rgba.length
31
+ });
32
+ }
33
+ return raster;
34
+ }
35
+ function dimensions(source2) {
36
+ return {
37
+ width: source2.naturalWidth ?? source2.videoWidth ?? source2.displayWidth ?? source2.width ?? 0,
38
+ height: source2.naturalHeight ?? source2.videoHeight ?? source2.displayHeight ?? source2.height ?? 0
39
+ };
40
+ }
41
+ function defaultCreateCanvas(width, height) {
42
+ if (typeof OffscreenCanvas === "function") return new OffscreenCanvas(width, height);
43
+ if (typeof document === "object") {
44
+ const canvas = document.createElement("canvas");
45
+ canvas.width = width;
46
+ canvas.height = height;
47
+ return canvas;
48
+ }
49
+ throw new PPDetectionError("INVALID_INPUT", "\u5F53\u524D\u73AF\u5883\u6CA1\u6709\u53EF\u7528\u7684 Canvas 2D \u5B9E\u73B0");
50
+ }
51
+ function invalidDecode(error) {
52
+ if (error instanceof PPDetectionError) return error;
53
+ const message = error instanceof Error ? error.message : String(error);
54
+ const cors = typeof DOMException !== "undefined" && error instanceof DOMException && error.name === "SecurityError" || /cors|cross.origin|taint|security/i.test(message);
55
+ return new PPDetectionError(
56
+ "INVALID_INPUT",
57
+ cors ? "\u65E0\u6CD5\u4ECE\u5A92\u4F53\u8BFB\u53D6\u50CF\u7D20\uFF0C\u8BF7\u68C0\u67E5 CORS \u54CD\u5E94\u5934\u548C Canvas \u8DE8\u57DF\u9650\u5236" : "\u65E0\u6CD5\u89E3\u7801\u6216\u8BFB\u53D6\u56FE\u7247\u50CF\u7D20",
58
+ { cors, causeMessage: message },
59
+ { cause: error }
60
+ );
61
+ }
62
+ async function decodeImageSource(input, environment = {}) {
63
+ const clock = environment.now ?? nowDefault;
64
+ const startedAt = clock();
65
+ throwIfAborted(environment.signal);
66
+ if (isImageDataLike(input)) {
67
+ const raster = validateRaster({
68
+ width: input.width,
69
+ height: input.height,
70
+ rgba: new Uint8ClampedArray(input.data ?? input.rgba)
71
+ });
72
+ return { ...raster, decodeMs: Math.max(0, clock() - startedAt) };
73
+ }
74
+ let ownedBitmap;
75
+ try {
76
+ let source2;
77
+ if (typeof Blob !== "undefined" && input instanceof Blob) {
78
+ const createBitmap = environment.createImageBitmap ?? globalThis.createImageBitmap;
79
+ if (typeof createBitmap !== "function") {
80
+ throw new PPDetectionError("INVALID_INPUT", "\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301 Blob \u56FE\u7247\u89E3\u7801");
81
+ }
82
+ ownedBitmap = await createBitmap(input);
83
+ source2 = ownedBitmap;
84
+ } else {
85
+ source2 = input;
86
+ }
87
+ throwIfAborted(environment.signal);
88
+ const { width, height } = dimensions(source2);
89
+ if (!Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0) {
90
+ throw new PPDetectionError("INVALID_INPUT", "\u56FE\u7247\u5BBD\u9AD8\u5FC5\u987B\u662F\u6B63\u6574\u6570", { width, height });
91
+ }
92
+ const canvas = (environment.createCanvas ?? defaultCreateCanvas)(width, height);
93
+ const context = canvas.getContext("2d", { willReadFrequently: true });
94
+ if (!context) throw new PPDetectionError("INVALID_INPUT", "\u65E0\u6CD5\u521B\u5EFA Canvas 2D \u4E0A\u4E0B\u6587");
95
+ context.drawImage(source2, 0, 0);
96
+ const rgba = new Uint8ClampedArray(context.getImageData(0, 0, width, height).data);
97
+ throwIfAborted(environment.signal);
98
+ const raster = validateRaster({ width, height, rgba });
99
+ return { ...raster, decodeMs: Math.max(0, clock() - startedAt) };
100
+ } catch (error) {
101
+ throw invalidDecode(error);
102
+ } finally {
103
+ ownedBitmap?.close?.();
104
+ }
105
+ }
106
+
107
+ // src/detection/nms.ts
108
+ function area(box) {
109
+ return Math.max(0, box.xMax - box.xMin) * Math.max(0, box.yMax - box.yMin);
110
+ }
111
+ function intersectionOverUnion(left, right) {
112
+ const intersection = Math.max(0, Math.min(left.xMax, right.xMax) - Math.max(left.xMin, right.xMin)) * Math.max(0, Math.min(left.yMax, right.yMax) - Math.max(left.yMin, right.yMin));
113
+ const union = area(left) + area(right) - intersection;
114
+ return union <= 0 ? 0 : intersection / union;
115
+ }
116
+ function nonMaximumSuppression(candidates, iouThreshold) {
117
+ const sorted = [...candidates].sort(
118
+ (left, right) => right.score - left.score || left.index - right.index
119
+ );
120
+ const selected = [];
121
+ for (const candidate of sorted) {
122
+ const suppressed = selected.some(
123
+ (kept) => kept.classId === candidate.classId && intersectionOverUnion(kept.box, candidate.box) > iouThreshold
124
+ );
125
+ if (!suppressed) selected.push(candidate);
126
+ }
127
+ return selected;
128
+ }
129
+
130
+ // src/detection/decode-output.ts
131
+ function tensorMap(value) {
132
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
133
+ throw new PPDetectionError("INFERENCE_FAILED", "\u6A21\u578B\u8F93\u51FA\u5FC5\u987B\u662F\u5F20\u91CF\u6620\u5C04");
134
+ }
135
+ const outputs = value;
136
+ for (const [name, tensor3] of Object.entries(outputs)) {
137
+ const data = tensor3?.data;
138
+ const dims = tensor3?.dims;
139
+ if (typeof tensor3 !== "object" || tensor3 === null || !("data" in tensor3) || !("dims" in tensor3) || !Array.isArray(dims) || dims.length === 0 || !dims.every((dimension) => Number.isInteger(dimension) && dimension >= 0) || typeof data?.length !== "number" || !Number.isSafeInteger(data.length) || data.length !== dims.reduce((size, dimension) => size * Number(dimension), 1)) {
140
+ throw new PPDetectionError("INFERENCE_FAILED", "\u6A21\u578B\u8F93\u51FA\u5F20\u91CF\u7ED3\u6784\u65E0\u6548", { name });
141
+ }
142
+ }
143
+ return outputs;
144
+ }
145
+ function thresholdFor(classId, options) {
146
+ const label = options.labels[classId] ?? String(classId);
147
+ return options.classThresholds?.[label] ?? options.scoreThreshold;
148
+ }
149
+ function sigmoid(value) {
150
+ return value >= 0 ? 1 / (1 + Math.exp(-value)) : Math.exp(value) / (1 + Math.exp(value));
151
+ }
152
+ function matchesContract(tensor3, contract) {
153
+ return tensor3.dims !== void 0 && tensor3.dims.length === contract.shape.length && tensor3.dims.every(
154
+ (dimension, index) => contract.shape[index] === -1 || dimension === contract.shape[index]
155
+ );
156
+ }
157
+ function declaredTensor(outputs, contracts, predicate) {
158
+ if (contracts === void 0) return void 0;
159
+ const matches = contracts.filter(predicate).map((contract) => {
160
+ const tensor3 = outputs[contract.name];
161
+ return tensor3 && matchesContract(tensor3, contract) ? tensor3 : void 0;
162
+ }).filter((tensor3) => tensor3 !== void 0);
163
+ return matches.length === 1 ? matches[0] : void 0;
164
+ }
165
+ function matrixCandidates(outputs, options) {
166
+ let output;
167
+ if (options.outputs !== void 0) {
168
+ output = declaredTensor(outputs, options.outputs, (tensor3) => tensor3.shape.at(-1) === 6);
169
+ } else {
170
+ output = Object.entries(outputs).find(([, tensor3]) => tensor3.dims?.at(-1) === 6)?.[1];
171
+ }
172
+ if (!output) return void 0;
173
+ const data = output.data;
174
+ const candidates = [];
175
+ for (let offset = 0, index = 0; offset + 5 < data.length; offset += 6, index += 1) {
176
+ const classId = Math.trunc(Number(data[offset]));
177
+ const score = Number(data[offset + 1]);
178
+ if (!Number.isFinite(score) || score < thresholdFor(classId, options)) continue;
179
+ const coordinates = [2, 3, 4, 5].map((item) => Number(data[offset + item]));
180
+ if (!coordinates.every(Number.isFinite)) continue;
181
+ const normalized = options.matrixCoordinates === "normalized";
182
+ candidates.push({
183
+ index,
184
+ classId,
185
+ score,
186
+ box: {
187
+ xMin: coordinates[0] * (normalized ? options.transform.inputWidth : 1),
188
+ yMin: coordinates[1] * (normalized ? options.transform.inputHeight : 1),
189
+ xMax: coordinates[2] * (normalized ? options.transform.inputWidth : 1),
190
+ yMax: coordinates[3] * (normalized ? options.transform.inputHeight : 1)
191
+ }
192
+ });
193
+ }
194
+ return candidates;
195
+ }
196
+ function findOutput(outputs, expression) {
197
+ return Object.entries(outputs).find(([name]) => expression.test(name))?.[1];
198
+ }
199
+ function queryCandidates(outputs, options) {
200
+ const boxes = options.outputs ? declaredTensor(outputs, options.outputs, (tensor3) => tensor3.shape.at(-1) === 4) : findOutput(outputs, /pred.*box|boxes/i);
201
+ const declaredLogitCandidates = options.outputs?.filter(
202
+ (tensor3) => tensor3.shape.at(-1) !== 4 && tensor3.shape.at(-1) !== 6 && tensor3.shape.length >= 2
203
+ );
204
+ const logits = options.outputs ? declaredTensor(
205
+ outputs,
206
+ declaredLogitCandidates,
207
+ (tensor3) => tensor3.shape.at(-1) === options.labels.length && !/(?:order|mask|aux)/i.test(tensor3.name)
208
+ ) ?? (declaredLogitCandidates?.length === 1 ? declaredTensor(outputs, declaredLogitCandidates, () => true) : void 0) : findOutput(outputs, /logit|score/i);
209
+ if (!logits || !boxes) return void 0;
210
+ const boxCount = Math.floor(boxes.data.length / 4);
211
+ if (boxCount === 0) return [];
212
+ const declaredClasses = logits.dims?.at(-1) ?? Math.floor(logits.data.length / boxCount);
213
+ const classes = Math.min(declaredClasses, options.labels.length);
214
+ if (classes <= 0) return [];
215
+ const candidates = [];
216
+ for (let query = 0; query < boxCount; query += 1) {
217
+ let classId = 0;
218
+ let score = -Infinity;
219
+ for (let candidateClass = 0; candidateClass < classes; candidateClass += 1) {
220
+ const candidateScore = sigmoid(Number(logits.data[query * declaredClasses + candidateClass]));
221
+ if (candidateScore > score) {
222
+ score = candidateScore;
223
+ classId = candidateClass;
224
+ }
225
+ }
226
+ if (score < thresholdFor(classId, options)) continue;
227
+ const offset = query * 4;
228
+ const raw = [
229
+ Number(boxes.data[offset]),
230
+ Number(boxes.data[offset + 1]),
231
+ Number(boxes.data[offset + 2]),
232
+ Number(boxes.data[offset + 3])
233
+ ];
234
+ if (!raw.every(Number.isFinite)) continue;
235
+ const normalized = options.queryCoordinates !== "pixels";
236
+ const xScale = normalized ? options.transform.inputWidth : 1;
237
+ const yScale = normalized ? options.transform.inputHeight : 1;
238
+ const xMin = options.queryBoxFormat === "xyxy" ? raw[0] : raw[0] - raw[2] / 2;
239
+ const yMin = options.queryBoxFormat === "xyxy" ? raw[1] : raw[1] - raw[3] / 2;
240
+ const xMax = options.queryBoxFormat === "xyxy" ? raw[2] : raw[0] + raw[2] / 2;
241
+ const yMax = options.queryBoxFormat === "xyxy" ? raw[3] : raw[1] + raw[3] / 2;
242
+ candidates.push({
243
+ index: query,
244
+ classId,
245
+ score,
246
+ box: {
247
+ xMin: xMin * xScale,
248
+ yMin: yMin * yScale,
249
+ xMax: xMax * xScale,
250
+ yMax: yMax * yScale
251
+ }
252
+ });
253
+ }
254
+ return candidates;
255
+ }
256
+ function clamp(value, minimum, maximum) {
257
+ return Math.min(maximum, Math.max(minimum, value));
258
+ }
259
+ function restoreBox(box, transform) {
260
+ const scaleX = transform.scaleX ?? transform.scale;
261
+ const scaleY = transform.scaleY ?? transform.scale;
262
+ const xMin = clamp((box.xMin - transform.padLeft) / scaleX, 0, transform.originalWidth);
263
+ const yMin = clamp((box.yMin - transform.padTop) / scaleY, 0, transform.originalHeight);
264
+ const xMax = clamp((box.xMax - transform.padLeft) / scaleX, 0, transform.originalWidth);
265
+ const yMax = clamp((box.yMax - transform.padTop) / scaleY, 0, transform.originalHeight);
266
+ if (xMax <= xMin || yMax <= yMin) return void 0;
267
+ return {
268
+ x: xMin,
269
+ y: yMin,
270
+ width: xMax - xMin,
271
+ height: yMax - yMin,
272
+ xMin,
273
+ yMin,
274
+ xMax,
275
+ yMax
276
+ };
277
+ }
278
+ function decodeDetectionOutputs(outputValue, options) {
279
+ const outputs = tensorMap(outputValue);
280
+ const candidates = matrixCandidates(outputs, options) ?? queryCandidates(outputs, options);
281
+ if (!candidates) {
282
+ throw new PPDetectionError("INFERENCE_FAILED", "\u4E0D\u652F\u6301\u5F53\u524D\u6A21\u578B\u7684\u68C0\u6D4B\u8F93\u51FA\u7B7E\u540D", {
283
+ outputs: Object.keys(outputs)
284
+ });
285
+ }
286
+ const restored = candidates.flatMap((candidate) => {
287
+ const box = restoreBox(candidate.box, options.transform);
288
+ return box ? [{ ...candidate, box }] : [];
289
+ });
290
+ return nonMaximumSuppression(restored, options.iouThreshold).map((candidate) => ({
291
+ index: candidate.index,
292
+ classId: candidate.classId,
293
+ labelId: candidate.classId,
294
+ label: options.labels[candidate.classId] ?? String(candidate.classId),
295
+ score: candidate.score,
296
+ box: candidate.box,
297
+ polygon: [
298
+ { x: candidate.box.xMin, y: candidate.box.yMin },
299
+ { x: candidate.box.xMax, y: candidate.box.yMin },
300
+ { x: candidate.box.xMax, y: candidate.box.yMax },
301
+ { x: candidate.box.xMin, y: candidate.box.yMax }
302
+ ]
303
+ }));
304
+ }
305
+
306
+ // src/detection/preprocess.ts
307
+ function sampleChannel(raster, x, y, channel) {
308
+ const sourceX = Math.min(raster.width - 1, Math.max(0, x));
309
+ const sourceY = Math.min(raster.height - 1, Math.max(0, y));
310
+ return raster.rgba[(sourceY * raster.width + sourceX) * 4 + channel];
311
+ }
312
+ function bilinearChannel(raster, targetX, targetY, resizedWidth, resizedHeight, channel) {
313
+ const sourceX = (targetX + 0.5) * raster.width / resizedWidth - 0.5;
314
+ const sourceY = (targetY + 0.5) * raster.height / resizedHeight - 0.5;
315
+ const x0 = Math.floor(sourceX);
316
+ const y0 = Math.floor(sourceY);
317
+ const dx = sourceX - x0;
318
+ const dy = sourceY - y0;
319
+ const top = sampleChannel(raster, x0, y0, channel) * (1 - dx) + sampleChannel(raster, x0 + 1, y0, channel) * dx;
320
+ const bottom = sampleChannel(raster, x0, y0 + 1, channel) * (1 - dx) + sampleChannel(raster, x0 + 1, y0 + 1, channel) * dx;
321
+ return top * (1 - dy) + bottom * dy;
322
+ }
323
+ var BICUBIC_PRECISION_BITS = 22;
324
+ var BICUBIC_PRECISION = 1 << BICUBIC_PRECISION_BITS;
325
+ var BICUBIC_ROUNDING = 1 << BICUBIC_PRECISION_BITS - 1;
326
+ function bicubicFilter(value) {
327
+ const x = Math.abs(value);
328
+ if (x < 1) return ((-0.5 + 2) * x - (-0.5 + 3)) * x * x + 1;
329
+ if (x < 2) return (((x - 5) * x + 8) * x - 4) * -0.5;
330
+ return 0;
331
+ }
332
+ function createBicubicAxis(inputSize, outputSize) {
333
+ const scale = inputSize / outputSize;
334
+ const filterScale = Math.max(scale, 1);
335
+ const support = 2 * filterScale;
336
+ const axis = [];
337
+ for (let output = 0; output < outputSize; output += 1) {
338
+ const center = (output + 0.5) * scale;
339
+ let start = Math.trunc(center - support + 0.5);
340
+ if (start < 0) start = 0;
341
+ let end = Math.trunc(center + support + 0.5);
342
+ if (end > inputSize) end = inputSize;
343
+ end -= start;
344
+ const weights = [];
345
+ let total = 0;
346
+ for (let index = 0; index < end; index += 1) {
347
+ const weight = bicubicFilter((index + start - center + 0.5) / filterScale);
348
+ weights.push(weight);
349
+ total += weight;
350
+ }
351
+ if (total !== 0) {
352
+ for (let index = 0; index < weights.length; index += 1) {
353
+ weights[index] = weights[index] / total;
354
+ }
355
+ }
356
+ axis.push({
357
+ bounds: [start, end],
358
+ coefficients: weights.map(
359
+ (weight) => Math.trunc(
360
+ weight < 0 ? -0.5 + weight * BICUBIC_PRECISION : 0.5 + weight * BICUBIC_PRECISION
361
+ )
362
+ )
363
+ });
364
+ }
365
+ return {
366
+ bounds: axis.map(({ bounds }) => bounds),
367
+ coefficients: axis.map(({ coefficients }) => coefficients)
368
+ };
369
+ }
370
+ function clipBicubic(value) {
371
+ const rounded = value >> BICUBIC_PRECISION_BITS;
372
+ return Math.max(0, Math.min(255, rounded));
373
+ }
374
+ function bicubicChannel(raster, resizedWidth, resizedHeight, channel) {
375
+ const horizontal = createBicubicAxis(raster.width, resizedWidth);
376
+ const vertical = createBicubicAxis(raster.height, resizedHeight);
377
+ const intermediate = new Uint8Array(raster.height * resizedWidth);
378
+ for (let y = 0; y < raster.height; y += 1) {
379
+ for (let x = 0; x < resizedWidth; x += 1) {
380
+ const [start, count] = horizontal.bounds[x];
381
+ const coefficients = horizontal.coefficients[x];
382
+ let sum = BICUBIC_ROUNDING;
383
+ for (let index = 0; index < count; index += 1) {
384
+ sum += raster.rgba[(y * raster.width + start + index) * 4 + channel] * coefficients[index];
385
+ }
386
+ intermediate[y * resizedWidth + x] = clipBicubic(sum);
387
+ }
388
+ }
389
+ const output = new Uint8Array(resizedWidth * resizedHeight);
390
+ for (let y = 0; y < resizedHeight; y += 1) {
391
+ const [start, count] = vertical.bounds[y];
392
+ const coefficients = vertical.coefficients[y];
393
+ for (let x = 0; x < resizedWidth; x += 1) {
394
+ let sum = BICUBIC_ROUNDING;
395
+ for (let index = 0; index < count; index += 1) {
396
+ sum += intermediate[(start + index) * resizedWidth + x] * coefficients[index];
397
+ }
398
+ output[y * resizedWidth + x] = clipBicubic(sum);
399
+ }
400
+ }
401
+ return output;
402
+ }
403
+ function preprocessImage(raster, preprocessing) {
404
+ const inputWidth = preprocessing.size.width;
405
+ const inputHeight = preprocessing.size.height;
406
+ const doResize = preprocessing.doResize ?? true;
407
+ if (!doResize && (raster.width > inputWidth || raster.height > inputHeight)) {
408
+ throw new PPDetectionError("INVALID_INPUT", "\u7981\u7528\u7F29\u653E\u65F6\uFF0C\u8F93\u5165\u56FE\u50CF\u5C3A\u5BF8\u4E0D\u80FD\u8D85\u8FC7\u6A21\u578B\u8F93\u5165\u5C3A\u5BF8", {
409
+ inputSize: { width: inputWidth, height: inputHeight },
410
+ imageSize: { width: raster.width, height: raster.height }
411
+ });
412
+ }
413
+ const resizeMode2 = preprocessing.resizeMode ?? "letterbox";
414
+ const scale = doResize ? Math.min(inputWidth / raster.width, inputHeight / raster.height) : 1;
415
+ const resizedWidth = doResize ? resizeMode2 === "stretch" ? inputWidth : Math.max(1, Math.min(inputWidth, Math.round(raster.width * scale))) : Math.min(inputWidth, raster.width);
416
+ const resizedHeight = doResize ? resizeMode2 === "stretch" ? inputHeight : Math.max(1, Math.min(inputHeight, Math.round(raster.height * scale))) : Math.min(inputHeight, raster.height);
417
+ const scaleX = resizedWidth / raster.width;
418
+ const scaleY = resizedHeight / raster.height;
419
+ const padLeft = resizeMode2 === "stretch" ? 0 : Math.floor((inputWidth - resizedWidth) / 2);
420
+ const padTop = resizeMode2 === "stretch" ? 0 : Math.floor((inputHeight - resizedHeight) / 2);
421
+ const plane = inputWidth * inputHeight;
422
+ const data = new Float32Array(plane * 3);
423
+ const normalize = preprocessing.doNormalize ?? true;
424
+ const rescale = preprocessing.doRescale ?? true;
425
+ const mean = normalize ? preprocessing.mean ?? [0, 0, 0] : [0, 0, 0];
426
+ const std = normalize ? preprocessing.std ?? [1, 1, 1] : [1, 1, 1];
427
+ const interpolation2 = preprocessing.interpolation ?? "bilinear";
428
+ for (let channel = 0; channel < 3; channel += 1) {
429
+ const padding = normalize && mean[channel] !== 0 ? -mean[channel] / std[channel] : 0;
430
+ data.fill(padding, channel * plane, (channel + 1) * plane);
431
+ const bicubic = doResize && interpolation2 === "bicubic" ? bicubicChannel(raster, resizedWidth, resizedHeight, channel) : void 0;
432
+ for (let y = 0; y < resizedHeight; y += 1) {
433
+ for (let x = 0; x < resizedWidth; x += 1) {
434
+ const pixel = bicubic ? bicubic[y * resizedWidth + x] : doResize ? bilinearChannel(raster, x, y, resizedWidth, resizedHeight, channel) : sampleChannel(raster, x, y, channel);
435
+ const scaled = rescale ? pixel * preprocessing.rescaleFactor : pixel;
436
+ data[channel * plane + (y + padTop) * inputWidth + x + padLeft] = normalize ? (scaled - mean[channel]) / std[channel] : scaled;
437
+ }
438
+ }
439
+ }
440
+ return {
441
+ data,
442
+ dims: [1, 3, inputHeight, inputWidth],
443
+ transform: {
444
+ inputWidth,
445
+ inputHeight,
446
+ originalWidth: raster.width,
447
+ originalHeight: raster.height,
448
+ resizedWidth,
449
+ resizedHeight,
450
+ scale,
451
+ scaleX,
452
+ scaleY,
453
+ padLeft,
454
+ padTop
455
+ }
456
+ };
457
+ }
458
+
459
+ // src/detection/detector.ts
460
+ function nowDefault2() {
461
+ return globalThis.performance?.now() ?? Date.now();
462
+ }
463
+ function elapsed(clock, startedAt) {
464
+ return Math.max(0, clock() - startedAt);
465
+ }
466
+ function throwIfAborted2(signal) {
467
+ if (signal?.aborted) {
468
+ throw new PPDetectionError("ABORTED", "\u68C0\u6D4B\u5DF2\u53D6\u6D88", { reason: signal.reason });
469
+ }
470
+ }
471
+ function validateThreshold(value, path) {
472
+ if (!Number.isFinite(value) || value < 0 || value > 1) {
473
+ throw new PPDetectionError("INVALID_INPUT", `${path} \u5FC5\u987B\u662F 0 \u5230 1 \u7684\u6709\u9650\u6570\u503C`, {
474
+ path,
475
+ value
476
+ });
477
+ }
478
+ }
479
+ function validateDetectOptions(labels, options) {
480
+ if (options.threshold !== void 0) validateThreshold(options.threshold, "threshold");
481
+ if (options.classThresholds === void 0) return;
482
+ for (const [label, value] of Object.entries(options.classThresholds)) {
483
+ if (!labels.includes(label)) {
484
+ throw new PPDetectionError("INVALID_INPUT", `classThresholds \u5305\u542B\u672A\u77E5\u7C7B\u522B ${label}`, {
485
+ label
486
+ });
487
+ }
488
+ validateThreshold(value, `classThresholds.${label}`);
489
+ }
490
+ }
491
+ var PPDetectionDetectorImplementation = class {
492
+ constructor(options) {
493
+ this.options = options;
494
+ this.capabilities = options.capabilities;
495
+ this.manifest = options.manifest;
496
+ this.model = options.model;
497
+ this.runtime = options.runtime;
498
+ this.loadTimings = options.loadTimings;
499
+ this.clock = options.now ?? nowDefault2;
500
+ }
501
+ options;
502
+ capabilities;
503
+ manifest;
504
+ model;
505
+ runtime;
506
+ loadTimings;
507
+ clock;
508
+ executor;
509
+ loadPromise;
510
+ disposePromise;
511
+ queue = Promise.resolve();
512
+ disposed = false;
513
+ async load(options = {}) {
514
+ if (this.disposed) throw new PPDetectionError("DISPOSED", "\u68C0\u6D4B\u5668\u5DF2\u91CA\u653E");
515
+ throwIfAborted2(options.signal);
516
+ if (this.executor) return;
517
+ this.loadPromise ??= this.options.loadExecutor(options.signal).then(async (executor) => {
518
+ if (this.disposed) {
519
+ await executor.dispose();
520
+ throw new PPDetectionError("DISPOSED", "\u68C0\u6D4B\u5668\u5DF2\u91CA\u653E");
521
+ }
522
+ this.executor = executor;
523
+ });
524
+ try {
525
+ await this.loadPromise;
526
+ } catch (error) {
527
+ this.loadPromise = void 0;
528
+ throw error;
529
+ }
530
+ }
531
+ detect(input, options = {}) {
532
+ if (this.disposed) return Promise.reject(new PPDetectionError("DISPOSED", "\u68C0\u6D4B\u5668\u5DF2\u91CA\u653E"));
533
+ if (!this.executor) {
534
+ return Promise.reject(new PPDetectionError("SESSION_CREATE_FAILED", "\u68C0\u6D4B\u5668\u5C1A\u672A\u52A0\u8F7D"));
535
+ }
536
+ try {
537
+ validateDetectOptions(this.manifest.labels, options);
538
+ } catch (error) {
539
+ return Promise.reject(
540
+ error instanceof Error ? error : new PPDetectionError("INVALID_INPUT", String(error))
541
+ );
542
+ }
543
+ const operation = this.queue.then(() => this.detectOnce(input, options));
544
+ this.queue = operation.then(
545
+ () => void 0,
546
+ () => void 0
547
+ );
548
+ return operation;
549
+ }
550
+ getCacheEstimate() {
551
+ return this.options.getCacheEstimate?.() ?? Promise.resolve({ bytes: 0, entries: 0 });
552
+ }
553
+ clearCurrentModelCache() {
554
+ return this.options.clearCurrentModelCache?.() ?? Promise.resolve();
555
+ }
556
+ clearAllCache() {
557
+ return this.options.clearAllCache?.() ?? Promise.resolve();
558
+ }
559
+ clearModelCache() {
560
+ return this.clearCurrentModelCache();
561
+ }
562
+ async dispose() {
563
+ if (this.disposePromise) return await this.disposePromise;
564
+ this.disposed = true;
565
+ this.disposePromise = (async () => {
566
+ await this.queue;
567
+ await this.executor?.dispose();
568
+ this.executor = void 0;
569
+ await this.options.disposeResources?.();
570
+ })();
571
+ await this.disposePromise;
572
+ }
573
+ async detectOnce(input, options) {
574
+ if (this.disposed) throw new PPDetectionError("DISPOSED", "\u68C0\u6D4B\u5668\u5DF2\u91CA\u653E");
575
+ const executor = this.executor;
576
+ if (!executor) throw new PPDetectionError("SESSION_CREATE_FAILED", "\u68C0\u6D4B\u5668\u5C1A\u672A\u52A0\u8F7D");
577
+ throwIfAborted2(options.signal);
578
+ const totalStartedAt = this.clock();
579
+ const decoded = await decodeImageSource(input, {
580
+ ...this.options.decodeEnvironment,
581
+ signal: options.signal,
582
+ now: this.clock
583
+ });
584
+ throwIfAborted2(options.signal);
585
+ this.options.onProgress?.({ phase: "preprocess", status: "start" });
586
+ const preprocessStartedAt = this.clock();
587
+ const preprocessed = preprocessImage(decoded, this.manifest.preprocessing);
588
+ const preprocessMs = elapsed(this.clock, preprocessStartedAt);
589
+ this.options.onProgress?.({ phase: "preprocess", status: "complete" });
590
+ this.options.onProgress?.({ phase: "inference", status: "start" });
591
+ const inferenceStartedAt = this.clock();
592
+ const outputs = await executor.run(
593
+ {
594
+ inputName: this.manifest.input.name,
595
+ data: preprocessed.data,
596
+ dims: preprocessed.dims
597
+ },
598
+ options.signal
599
+ );
600
+ const inferenceMs = elapsed(this.clock, inferenceStartedAt);
601
+ this.options.onProgress?.({ phase: "inference", status: "complete" });
602
+ throwIfAborted2(options.signal);
603
+ this.options.onProgress?.({ phase: "postprocess", status: "start" });
604
+ const postprocessStartedAt = this.clock();
605
+ const detections = decodeDetectionOutputs(outputs, {
606
+ labels: this.manifest.labels,
607
+ scoreThreshold: options.threshold ?? this.manifest.postprocessing.scoreThreshold,
608
+ classThresholds: options.classThresholds,
609
+ iouThreshold: this.manifest.postprocessing.iouThreshold,
610
+ transform: preprocessed.transform,
611
+ outputs: this.manifest.outputs,
612
+ matrixCoordinates: this.manifest.postprocessing.matrixCoordinates,
613
+ queryCoordinates: this.manifest.postprocessing.queryCoordinates,
614
+ queryBoxFormat: this.manifest.postprocessing.queryBoxFormat
615
+ });
616
+ const postprocessMs = elapsed(this.clock, postprocessStartedAt);
617
+ this.options.onProgress?.({ phase: "postprocess", status: "complete" });
618
+ return {
619
+ detections,
620
+ image: {
621
+ input: this.manifest.preprocessing.size,
622
+ original: { width: decoded.width, height: decoded.height }
623
+ },
624
+ model: this.model,
625
+ runtime: this.runtime,
626
+ timings: {
627
+ decodeMs: decoded.decodeMs,
628
+ preprocessMs,
629
+ inferenceMs,
630
+ postprocessMs,
631
+ totalMs: elapsed(this.clock, totalStartedAt)
632
+ },
633
+ ...options.timestampMs === void 0 && options.metadata === void 0 ? {} : { frame: { timestampMs: options.timestampMs, metadata: options.metadata } }
634
+ };
635
+ }
636
+ };
637
+
638
+ // src/model/manifest.ts
639
+ var SOURCE_KINDS = /* @__PURE__ */ new Set(["git-lfs", "huggingface", "modelscope", "custom"]);
640
+ var BACKENDS = /* @__PURE__ */ new Set(["wasm", "webgpu"]);
641
+ var PRECISIONS = /* @__PURE__ */ new Set(["fp32", "fp16", "int8", "int4", "fp8"]);
642
+ var REVISION_PATTERN = /^[a-fA-F0-9]{40,64}$/;
643
+ var SHA256_PATTERN = /^[a-fA-F0-9]{64}$/;
644
+ function isRecord(value) {
645
+ return typeof value === "object" && value !== null && !Array.isArray(value);
646
+ }
647
+ function invalid(path, message) {
648
+ throw new PPDetectionError("INVALID_MANIFEST", `\u6A21\u578B\u6E05\u5355 ${path} ${message}`, { path });
649
+ }
650
+ function record(value, path) {
651
+ if (!isRecord(value)) invalid(path, "\u5FC5\u987B\u662F\u5BF9\u8C61");
652
+ return value;
653
+ }
654
+ function text(value, path) {
655
+ if (typeof value !== "string" || value.trim() === "") invalid(path, "\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
656
+ return value;
657
+ }
658
+ function positiveInteger(value, path) {
659
+ if (!Number.isInteger(value) || value <= 0) invalid(path, "\u5FC5\u987B\u662F\u6B63\u6574\u6570");
660
+ return value;
661
+ }
662
+ function nonNegativeIntegerOrNull(value, path) {
663
+ if (value === null) return null;
664
+ if (!Number.isInteger(value) || value < 0) invalid(path, "\u5FC5\u987B\u662F\u975E\u8D1F\u6574\u6570\u6216 null");
665
+ return value;
666
+ }
667
+ function threshold(value, path) {
668
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1)
669
+ invalid(path, "\u5FC5\u987B\u662F 0 \u5230 1 \u7684\u6709\u9650\u6570\u503C");
670
+ return value;
671
+ }
672
+ function optionalCoordinate(value, path) {
673
+ if (value === void 0) return void 0;
674
+ if (value === "pixels") return "pixels";
675
+ if (value === "normalized") return "normalized";
676
+ invalid(path, "\u5FC5\u987B\u662F pixels \u6216 normalized");
677
+ }
678
+ function optionalBoxFormat(value, path) {
679
+ if (value === void 0) return void 0;
680
+ if (value === "cxcywh") return "cxcywh";
681
+ if (value === "xyxy") return "xyxy";
682
+ invalid(path, "\u5FC5\u987B\u662F cxcywh \u6216 xyxy");
683
+ }
684
+ function optionalResizeMode(value, path) {
685
+ if (value === void 0) return void 0;
686
+ if (value === "letterbox" || value === "stretch") return value;
687
+ invalid(path, "\u5FC5\u987B\u662F letterbox \u6216 stretch");
688
+ }
689
+ function optionalInterpolation(value, path) {
690
+ if (value === void 0) return void 0;
691
+ if (value === "bilinear" || value === "bicubic") return value;
692
+ invalid(path, "\u5FC5\u987B\u662F bilinear \u6216 bicubic");
693
+ }
694
+ function optionalFiniteNumbers(value, path) {
695
+ if (value === void 0) return void 0;
696
+ if (!Array.isArray(value) || value.length === 0 || !value.every((item) => typeof item === "number" && Number.isFinite(item))) {
697
+ invalid(path, "\u5FC5\u987B\u662F\u975E\u7A7A\u7684\u6709\u9650\u6570\u503C\u6570\u7EC4");
698
+ }
699
+ return value.map((item) => item);
700
+ }
701
+ function tensor(value, path, allowDynamic = false) {
702
+ const candidate = record(value, path);
703
+ const shape = candidate.shape;
704
+ if (!Array.isArray(shape) || shape.length === 0 || !shape.every(
705
+ (dimension) => Number.isInteger(dimension) && (dimension > 0 || allowDynamic && dimension === -1)
706
+ )) {
707
+ invalid(`${path}.shape`, "\u5FC5\u987B\u662F\u975E\u7A7A\u7684\u6B63\u6574\u6570\u6570\u7EC4");
708
+ }
709
+ return {
710
+ name: text(candidate.name, `${path}.name`),
711
+ shape: shape.map((dimension) => dimension),
712
+ dtype: text(candidate.dtype, `${path}.dtype`)
713
+ };
714
+ }
715
+ function source(value, path, variantBytes) {
716
+ const candidate = record(value, path);
717
+ if (typeof candidate.kind !== "string" || !SOURCE_KINDS.has(candidate.kind))
718
+ invalid(`${path}.kind`, "\u4E0D\u53D7\u652F\u6301");
719
+ const revision = text(candidate.revision, `${path}.revision`);
720
+ if (!REVISION_PATTERN.test(revision))
721
+ invalid(`${path}.revision`, "\u5FC5\u987B\u662F 40 \u81F3 64 \u4F4D\u5341\u516D\u8FDB\u5236\u4E0D\u53EF\u53D8 revision");
722
+ const downloadUrl = text(candidate.downloadUrl, `${path}.downloadUrl`);
723
+ let parsedUrl;
724
+ try {
725
+ parsedUrl = new URL(downloadUrl);
726
+ } catch {
727
+ invalid(`${path}.downloadUrl`, "\u5FC5\u987B\u662F\u6709\u6548 URL");
728
+ }
729
+ if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:" || parsedUrl.hostname === "") {
730
+ invalid(`${path}.downloadUrl`, "\u5FC5\u987B\u662F\u542B\u4E3B\u673A\u7684 HTTP(S) URL");
731
+ }
732
+ const bytes = positiveInteger(candidate.bytes, `${path}.bytes`);
733
+ if (bytes !== variantBytes) invalid(`${path}.bytes`, "\u5FC5\u987B\u4E0E\u53D8\u4F53\u5927\u5C0F\u4E00\u81F4");
734
+ const sha256 = text(candidate.sha256, `${path}.sha256`).toLowerCase();
735
+ if (!SHA256_PATTERN.test(sha256)) invalid(`${path}.sha256`, "\u5FC5\u987B\u662F 64 \u4F4D\u5341\u516D\u8FDB\u5236\u6458\u8981");
736
+ return {
737
+ kind: candidate.kind,
738
+ repository: text(candidate.repository, `${path}.repository`),
739
+ revision,
740
+ path: text(candidate.path, `${path}.path`),
741
+ downloadUrl,
742
+ bytes,
743
+ sha256
744
+ };
745
+ }
746
+ function variant(value, path) {
747
+ const candidate = record(value, path);
748
+ if (typeof candidate.precision !== "string" || !PRECISIONS.has(candidate.precision))
749
+ invalid(`${path}.precision`, "\u4E0D\u53D7\u652F\u6301");
750
+ if (candidate.quantization !== null && (typeof candidate.quantization !== "string" || candidate.quantization.trim() === "")) {
751
+ invalid(`${path}.quantization`, "\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32\u6216 null");
752
+ }
753
+ if (!Array.isArray(candidate.backends) || candidate.backends.length === 0 || !candidate.backends.every(
754
+ (backend) => typeof backend === "string" && BACKENDS.has(backend)
755
+ )) {
756
+ invalid(`${path}.backends`, "\u5FC5\u987B\u662F\u975E\u7A7A\u7684 wasm/webgpu \u6570\u7EC4");
757
+ }
758
+ const bytes = positiveInteger(candidate.bytes, `${path}.bytes`);
759
+ if (!Array.isArray(candidate.sources) || candidate.sources.length === 0)
760
+ invalid(`${path}.sources`, "\u5FC5\u987B\u662F\u975E\u7A7A\u6570\u7EC4");
761
+ const sources = candidate.sources.map(
762
+ (item, index) => source(item, `${path}.sources[${index}]`, bytes)
763
+ );
764
+ if (new Set(sources.map((item) => item.kind)).size !== sources.length)
765
+ invalid(`${path}.sources`, "\u540C\u4E00\u53D8\u4F53\u4E2D\u7684\u6765\u6E90 kind \u4E0D\u5F97\u91CD\u590D");
766
+ const backends = candidate.backends.map((backend) => backend);
767
+ const status = candidate.status;
768
+ if (status !== void 0 && status !== "stable" && status !== "labs" && status !== "blocked")
769
+ invalid(`${path}.status`, "\u4E0D\u53D7\u652F\u6301");
770
+ return {
771
+ id: text(candidate.id, `${path}.id`),
772
+ precision: candidate.precision,
773
+ quantization: candidate.quantization === null ? null : candidate.quantization,
774
+ opset: positiveInteger(candidate.opset, `${path}.opset`),
775
+ bytes,
776
+ parameterCount: nonNegativeIntegerOrNull(candidate.parameterCount, `${path}.parameterCount`),
777
+ backends,
778
+ sources,
779
+ ...status === void 0 ? {} : { status }
780
+ };
781
+ }
782
+ function parseDetectionManifest(value) {
783
+ const candidate = record(value, "\u6839\u8282\u70B9");
784
+ if (candidate.schemaVersion !== 1) invalid("schemaVersion", "\u5FC5\u987B\u662F 1");
785
+ const modelValue = record(candidate.model, "model");
786
+ const model = {
787
+ id: text(modelValue.id, "model.id"),
788
+ version: text(modelValue.version, "model.version")
789
+ };
790
+ if (!Array.isArray(candidate.outputs) || candidate.outputs.length === 0)
791
+ invalid("outputs", "\u5FC5\u987B\u662F\u975E\u7A7A\u6570\u7EC4");
792
+ const preprocessing = record(candidate.preprocessing, "preprocessing");
793
+ const size = record(preprocessing.size, "preprocessing.size");
794
+ if (typeof preprocessing.rescaleFactor !== "number" || !Number.isFinite(preprocessing.rescaleFactor) || preprocessing.rescaleFactor <= 0) {
795
+ invalid("preprocessing.rescaleFactor", "\u5FC5\u987B\u662F\u6B63\u6709\u9650\u6570\u503C");
796
+ }
797
+ for (const field of ["doResize", "doRescale", "doNormalize"]) {
798
+ if (preprocessing[field] !== void 0 && typeof preprocessing[field] !== "boolean")
799
+ invalid(`preprocessing.${field}`, "\u5FC5\u987B\u662F\u5E03\u5C14\u503C");
800
+ }
801
+ const postprocessing = record(candidate.postprocessing, "postprocessing");
802
+ if (postprocessing.type !== "nms") invalid("postprocessing.type", "\u76EE\u524D\u53EA\u652F\u6301 nms");
803
+ const matrixCoordinates = optionalCoordinate(
804
+ postprocessing.matrixCoordinates,
805
+ "postprocessing.matrixCoordinates"
806
+ );
807
+ const queryCoordinates = optionalCoordinate(
808
+ postprocessing.queryCoordinates,
809
+ "postprocessing.queryCoordinates"
810
+ );
811
+ const queryBoxFormat = optionalBoxFormat(
812
+ postprocessing.queryBoxFormat,
813
+ "postprocessing.queryBoxFormat"
814
+ );
815
+ const resizeMode2 = optionalResizeMode(preprocessing.resizeMode, "preprocessing.resizeMode");
816
+ const interpolation2 = optionalInterpolation(
817
+ preprocessing.interpolation,
818
+ "preprocessing.interpolation"
819
+ );
820
+ if (!Array.isArray(candidate.labels) || candidate.labels.length === 0 || !candidate.labels.every((label) => typeof label === "string" && label.trim() !== "")) {
821
+ invalid("labels", "\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32\u6570\u7EC4");
822
+ }
823
+ if (!Array.isArray(candidate.variants) || candidate.variants.length === 0)
824
+ invalid("variants", "\u5FC5\u987B\u662F\u975E\u7A7A\u6570\u7EC4");
825
+ const variants = candidate.variants.map((item, index) => variant(item, `variants[${index}]`));
826
+ if (new Set(variants.map((item) => item.id)).size !== variants.length)
827
+ invalid("variants", "\u53D8\u4F53 id \u4E0D\u5F97\u91CD\u590D");
828
+ const input = tensor(candidate.input, "input");
829
+ const mean = optionalFiniteNumbers(preprocessing.mean, "preprocessing.mean");
830
+ const std = optionalFiniteNumbers(preprocessing.std, "preprocessing.std");
831
+ const channels = input.shape[1];
832
+ if (mean !== void 0 && mean.length !== channels)
833
+ invalid("preprocessing.mean", "\u957F\u5EA6\u5FC5\u987B\u4E0E\u8F93\u5165\u901A\u9053\u6570\u4E00\u81F4");
834
+ if (std !== void 0 && std.length !== channels)
835
+ invalid("preprocessing.std", "\u957F\u5EA6\u5FC5\u987B\u4E0E\u8F93\u5165\u901A\u9053\u6570\u4E00\u81F4");
836
+ if (std?.some((value2) => value2 <= 0)) invalid("preprocessing.std", "\u5FC5\u987B\u5168\u90E8\u5927\u4E8E\u96F6");
837
+ return {
838
+ schemaVersion: 1,
839
+ model,
840
+ input,
841
+ outputs: candidate.outputs.map((item, index) => tensor(item, `outputs[${index}]`, true)),
842
+ preprocessing: {
843
+ size: {
844
+ width: positiveInteger(size.width, "preprocessing.size.width"),
845
+ height: positiveInteger(size.height, "preprocessing.size.height")
846
+ },
847
+ rescaleFactor: preprocessing.rescaleFactor,
848
+ ...resizeMode2 === void 0 ? {} : { resizeMode: resizeMode2 },
849
+ ...interpolation2 === void 0 ? {} : { interpolation: interpolation2 },
850
+ ...typeof preprocessing.doResize === "boolean" ? { doResize: preprocessing.doResize } : {},
851
+ ...typeof preprocessing.doRescale === "boolean" ? { doRescale: preprocessing.doRescale } : {},
852
+ ...typeof preprocessing.doNormalize === "boolean" ? { doNormalize: preprocessing.doNormalize } : {},
853
+ ...mean === void 0 ? {} : { mean },
854
+ ...std === void 0 ? {} : { std }
855
+ },
856
+ postprocessing: {
857
+ type: "nms",
858
+ scoreThreshold: threshold(postprocessing.scoreThreshold, "postprocessing.scoreThreshold"),
859
+ iouThreshold: threshold(postprocessing.iouThreshold, "postprocessing.iouThreshold"),
860
+ ...matrixCoordinates === void 0 ? {} : { matrixCoordinates },
861
+ ...queryCoordinates === void 0 ? {} : { queryCoordinates },
862
+ ...queryBoxFormat === void 0 ? {} : { queryBoxFormat }
863
+ },
864
+ labels: candidate.labels.map((label) => label),
865
+ variants
866
+ };
867
+ }
868
+
869
+ // src/model/public-manifest.ts
870
+ var SHA256_PATTERN2 = /^[a-fA-F0-9]{64}$/;
871
+ var BACKENDS2 = /* @__PURE__ */ new Set(["wasm", "webgpu"]);
872
+ var PRECISIONS2 = /* @__PURE__ */ new Set(["fp32", "fp16", "int8", "int4", "fp8"]);
873
+ function record2(value, path) {
874
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
875
+ throw new PPDetectionError("INVALID_MANIFEST", `\u6A21\u578B\u6E05\u5355 ${path} \u5FC5\u987B\u662F\u5BF9\u8C61`, { path });
876
+ }
877
+ return value;
878
+ }
879
+ function invalid2(path, message) {
880
+ throw new PPDetectionError("INVALID_MANIFEST", `\u6A21\u578B\u6E05\u5355 ${path} ${message}`, { path });
881
+ }
882
+ function text2(value, path) {
883
+ if (typeof value !== "string" || value.trim() === "") invalid2(path, "\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
884
+ return value;
885
+ }
886
+ function integer(value, path, allowZero = false) {
887
+ if (!Number.isInteger(value) || (allowZero ? value < 0 : value <= 0)) {
888
+ invalid2(path, allowZero ? "\u5FC5\u987B\u662F\u975E\u8D1F\u6574\u6570" : "\u5FC5\u987B\u662F\u6B63\u6574\u6570");
889
+ }
890
+ return value;
891
+ }
892
+ function nonNegativeIntegerOrNull2(value, path) {
893
+ if (value === null) return null;
894
+ return integer(value, path, true);
895
+ }
896
+ function finite(value, path) {
897
+ if (typeof value !== "number" || !Number.isFinite(value)) invalid2(path, "\u5FC5\u987B\u662F\u6709\u9650\u6570\u503C");
898
+ return value;
899
+ }
900
+ function boolean(value, path) {
901
+ if (typeof value !== "boolean") invalid2(path, "\u5FC5\u987B\u662F\u5E03\u5C14\u503C");
902
+ return value;
903
+ }
904
+ function resizeMode(value, path) {
905
+ if (value === void 0) return void 0;
906
+ if (value === "letterbox" || value === "stretch") return value;
907
+ invalid2(path, "\u5FC5\u987B\u662F letterbox \u6216 stretch");
908
+ }
909
+ function interpolation(value, path) {
910
+ if (value === void 0) return void 0;
911
+ if (value === "bilinear" || value === "bicubic") return value;
912
+ invalid2(path, "\u5FC5\u987B\u662F bilinear \u6216 bicubic");
913
+ }
914
+ function resample(value, path) {
915
+ const result = integer(value, path, true);
916
+ if (result === 2 || result === 3) return result;
917
+ invalid2(path, "\u76EE\u524D\u53EA\u652F\u6301 2 (bilinear) \u6216 3 (bicubic)");
918
+ }
919
+ function url(value, path) {
920
+ const result = text2(value, path);
921
+ try {
922
+ const parsed = new URL(result);
923
+ if (!["http:", "https:"].includes(parsed.protocol) || !parsed.hostname) throw new Error();
924
+ } catch {
925
+ invalid2(path, "\u5FC5\u987B\u662F\u542B\u4E3B\u673A\u7684 HTTP(S) URL");
926
+ }
927
+ return result;
928
+ }
929
+ function tensor2(value, path, allowDynamic = false) {
930
+ const candidate = record2(value, path);
931
+ if (!Array.isArray(candidate.shape) || candidate.shape.length === 0 || !candidate.shape.every(
932
+ (dimension) => Number.isInteger(dimension) && (dimension > 0 || allowDynamic && dimension === -1)
933
+ )) {
934
+ invalid2(`${path}.shape`, "\u5FC5\u987B\u662F\u6B63\u6574\u6570\u6570\u7EC4");
935
+ }
936
+ return {
937
+ name: text2(candidate.name, `${path}.name`),
938
+ dtype: text2(candidate.dtype, `${path}.dtype`),
939
+ shape: candidate.shape.map(Number)
940
+ };
941
+ }
942
+ function triple(value, path, positive = false) {
943
+ if (!Array.isArray(value) || value.length !== 3) invalid2(path, "\u5FC5\u987B\u5305\u542B 3 \u4E2A\u6570\u503C");
944
+ const result = value.map((item, index) => finite(item, `${path}[${index}]`));
945
+ if (positive && result.some((item) => item <= 0)) invalid2(path, "\u5FC5\u987B\u5168\u90E8\u5927\u4E8E\u96F6");
946
+ return result;
947
+ }
948
+ function variant2(value, path) {
949
+ const candidate = record2(value, path);
950
+ if (!Array.isArray(candidate.backendCompatibility) || candidate.backendCompatibility.length === 0 || !candidate.backendCompatibility.every(
951
+ (backend) => typeof backend === "string" && BACKENDS2.has(backend)
952
+ )) {
953
+ invalid2(`${path}.backendCompatibility`, "\u5FC5\u987B\u662F\u975E\u7A7A\u7684 wasm/webgpu \u6570\u7EC4");
954
+ }
955
+ if (typeof candidate.precision !== "string" || !PRECISIONS2.has(candidate.precision)) {
956
+ invalid2(`${path}.precision`, "\u4E0D\u53D7\u652F\u6301");
957
+ }
958
+ if (candidate.quantization !== void 0 && candidate.quantization !== null && (typeof candidate.quantization !== "string" || candidate.quantization.trim() === "")) {
959
+ invalid2(`${path}.quantization`, "\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32\u6216 null");
960
+ }
961
+ const sha256 = text2(candidate.sha256, `${path}.sha256`).toLowerCase();
962
+ if (!SHA256_PATTERN2.test(sha256)) invalid2(`${path}.sha256`, "\u5FC5\u987B\u662F 64 \u4F4D\u5341\u516D\u8FDB\u5236\u6458\u8981");
963
+ const validation = record2(candidate.validation, `${path}.validation`);
964
+ return {
965
+ backendCompatibility: candidate.backendCompatibility.map((backend) => backend),
966
+ bytes: integer(candidate.bytes, `${path}.bytes`),
967
+ filename: text2(candidate.filename, `${path}.filename`),
968
+ id: text2(candidate.id, `${path}.id`),
969
+ opset: integer(candidate.opset, `${path}.opset`),
970
+ precision: candidate.precision,
971
+ quantization: candidate.quantization === void 0 ? null : candidate.quantization,
972
+ sha256,
973
+ url: url(candidate.url, `${path}.url`),
974
+ validation: {
975
+ included: boolean(validation.included, `${path}.validation.included`),
976
+ pass: boolean(validation.pass, `${path}.validation.pass`),
977
+ report: text2(validation.report, `${path}.validation.report`)
978
+ }
979
+ };
980
+ }
981
+ function parseModelManifest(value) {
982
+ const candidate = record2(value, "\u6839\u8282\u70B9");
983
+ if (candidate.schemaVersion !== 1) invalid2("schemaVersion", "\u5FC5\u987B\u662F 1");
984
+ const model = record2(candidate.model, "model");
985
+ const preprocessing = record2(candidate.preprocessing, "preprocessing");
986
+ const size = record2(preprocessing.size, "preprocessing.size");
987
+ const source2 = record2(candidate.source, "source");
988
+ const files = record2(source2.files, "source.files");
989
+ const preprocessingInterpolation = interpolation(
990
+ preprocessing.interpolation,
991
+ "preprocessing.interpolation"
992
+ );
993
+ const preprocessingResample = resample(preprocessing.resample, "preprocessing.resample");
994
+ const input = tensor2(candidate.input, "input");
995
+ if (input.shape.length !== 4 || input.shape[0] !== 1 || input.shape[1] !== 3) {
996
+ invalid2("input.shape", "\u76EE\u524D\u53EA\u652F\u6301 [1,3,H,W]");
997
+ }
998
+ if (!Array.isArray(candidate.outputs) || candidate.outputs.length === 0) {
999
+ invalid2("outputs", "\u5FC5\u987B\u662F\u975E\u7A7A\u6570\u7EC4");
1000
+ }
1001
+ if (!Array.isArray(candidate.labels) || !candidate.labels.length)
1002
+ invalid2("labels", "\u5FC5\u987B\u662F\u975E\u7A7A\u6570\u7EC4");
1003
+ const labels = candidate.labels.map((label, index) => text2(label, `labels[${index}]`));
1004
+ if (!Array.isArray(candidate.variants) || !candidate.variants.length) {
1005
+ invalid2("variants", "\u5FC5\u987B\u662F\u975E\u7A7A\u6570\u7EC4");
1006
+ }
1007
+ const variants = candidate.variants.map((item, index) => variant2(item, `variants[${index}]`));
1008
+ if (new Set(variants.map((item) => item.id)).size !== variants.length)
1009
+ invalid2("variants", "id \u4E0D\u5F97\u91CD\u590D");
1010
+ if (!Array.isArray(candidate.variantPriority) || !candidate.variantPriority.length) {
1011
+ invalid2("variantPriority", "\u5FC5\u987B\u662F\u975E\u7A7A\u6570\u7EC4");
1012
+ }
1013
+ const variantPriority = candidate.variantPriority.map(
1014
+ (item, index) => text2(item, `variantPriority[${index}]`)
1015
+ );
1016
+ if (variantPriority.some((id) => !variants.some((item) => item.id === id))) {
1017
+ invalid2("variantPriority", "\u5F15\u7528\u4E86\u4E0D\u5B58\u5728\u7684\u53D8\u4F53");
1018
+ }
1019
+ const normalizedFiles = Object.fromEntries(
1020
+ Object.entries(files).map(([name, hash]) => {
1021
+ const normalized = text2(hash, `source.files.${name}`).toLowerCase();
1022
+ if (!SHA256_PATTERN2.test(normalized)) invalid2(`source.files.${name}`, "\u5FC5\u987B\u662F SHA-256");
1023
+ return [name, normalized];
1024
+ })
1025
+ );
1026
+ return {
1027
+ schemaVersion: 1,
1028
+ minSdkVersion: text2(candidate.minSdkVersion, "minSdkVersion"),
1029
+ model: {
1030
+ architecture: text2(model.architecture, "model.architecture"),
1031
+ id: text2(model.id, "model.id"),
1032
+ modelType: text2(model.modelType, "model.modelType"),
1033
+ parameterCount: nonNegativeIntegerOrNull2(model.parameterCount, "model.parameterCount"),
1034
+ version: text2(model.version, "model.version")
1035
+ },
1036
+ input,
1037
+ outputs: candidate.outputs.map((item, index) => tensor2(item, `outputs[${index}]`, true)),
1038
+ preprocessing: {
1039
+ doNormalize: boolean(preprocessing.doNormalize, "preprocessing.doNormalize"),
1040
+ doRescale: boolean(preprocessing.doRescale, "preprocessing.doRescale"),
1041
+ doResize: boolean(preprocessing.doResize, "preprocessing.doResize"),
1042
+ ...resizeMode(preprocessing.resizeMode, "preprocessing.resizeMode") === void 0 ? {} : { resizeMode: resizeMode(preprocessing.resizeMode, "preprocessing.resizeMode") },
1043
+ ...preprocessingInterpolation === void 0 ? {} : { interpolation: preprocessingInterpolation },
1044
+ imageMean: triple(preprocessing.imageMean, "preprocessing.imageMean"),
1045
+ imageStd: triple(preprocessing.imageStd, "preprocessing.imageStd", true),
1046
+ resample: preprocessingResample,
1047
+ rescaleFactor: finite(preprocessing.rescaleFactor, "preprocessing.rescaleFactor"),
1048
+ size: {
1049
+ height: integer(size.height, "preprocessing.size.height"),
1050
+ width: integer(size.width, "preprocessing.size.width")
1051
+ }
1052
+ },
1053
+ source: {
1054
+ files: normalizedFiles,
1055
+ license: text2(source2.license, "source.license"),
1056
+ name: text2(source2.name, "source.name"),
1057
+ url: url(source2.url, "source.url")
1058
+ },
1059
+ labels,
1060
+ variantPriority,
1061
+ variants
1062
+ };
1063
+ }
1064
+ function sourceKind(downloadUrl) {
1065
+ const hostname = new URL(downloadUrl).hostname.toLowerCase();
1066
+ if (hostname.includes("huggingface.co")) return "huggingface";
1067
+ if (hostname.includes("modelscope")) return "modelscope";
1068
+ return "custom";
1069
+ }
1070
+ function adaptModelManifest(manifest) {
1071
+ const preprocessingResample = resample(manifest.preprocessing.resample, "preprocessing.resample");
1072
+ const priority = new Map(manifest.variantPriority.map((id, index) => [id, index]));
1073
+ const variants = [...manifest.variants].sort(
1074
+ (left, right) => (priority.get(left.id) ?? manifest.variantPriority.length + manifest.variants.indexOf(left)) - (priority.get(right.id) ?? manifest.variantPriority.length + manifest.variants.indexOf(right))
1075
+ );
1076
+ return parseDetectionManifest({
1077
+ schemaVersion: 1,
1078
+ model: { id: manifest.model.id, version: manifest.model.version },
1079
+ input: manifest.input,
1080
+ outputs: manifest.outputs,
1081
+ preprocessing: {
1082
+ size: manifest.preprocessing.size,
1083
+ rescaleFactor: manifest.preprocessing.rescaleFactor,
1084
+ doResize: manifest.preprocessing.doResize,
1085
+ resizeMode: manifest.preprocessing.resizeMode,
1086
+ interpolation: manifest.preprocessing.interpolation ?? (preprocessingResample === 3 ? "bicubic" : "bilinear"),
1087
+ doRescale: manifest.preprocessing.doRescale,
1088
+ doNormalize: manifest.preprocessing.doNormalize,
1089
+ mean: manifest.preprocessing.imageMean,
1090
+ std: manifest.preprocessing.imageStd
1091
+ },
1092
+ postprocessing: { type: "nms", scoreThreshold: 0.5, iouThreshold: 0.5 },
1093
+ labels: manifest.labels,
1094
+ variants: variants.map((variant3) => ({
1095
+ id: variant3.id,
1096
+ precision: variant3.precision,
1097
+ quantization: variant3.quantization ?? null,
1098
+ opset: variant3.opset,
1099
+ bytes: variant3.bytes,
1100
+ parameterCount: manifest.model.parameterCount,
1101
+ backends: variant3.backendCompatibility,
1102
+ status: variant3.validation.included && variant3.validation.pass ? "stable" : "blocked",
1103
+ sources: [
1104
+ {
1105
+ kind: sourceKind(variant3.url),
1106
+ repository: manifest.source.url,
1107
+ revision: variant3.sha256,
1108
+ path: variant3.filename,
1109
+ downloadUrl: variant3.url,
1110
+ bytes: variant3.bytes,
1111
+ sha256: variant3.sha256
1112
+ }
1113
+ ]
1114
+ }))
1115
+ });
1116
+ }
1117
+
1118
+ // src/cache/indexeddb-cache.ts
1119
+ var IndexedDBModelCache = class {
1120
+ factory;
1121
+ databaseName;
1122
+ database;
1123
+ constructor(options = {}) {
1124
+ const factory = options.indexedDB ?? globalThis.indexedDB;
1125
+ if (!factory) throw new Error("\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301 IndexedDB");
1126
+ this.factory = factory;
1127
+ this.databaseName = options.databaseName ?? "web-sdk-pp-detection-models-v1";
1128
+ }
1129
+ async get(key) {
1130
+ const record3 = await this.request(
1131
+ "readonly",
1132
+ (store) => store.get(key)
1133
+ );
1134
+ return record3?.bytes.slice(0);
1135
+ }
1136
+ async put(key, bytes) {
1137
+ const stored = bytes.slice(0);
1138
+ await this.request(
1139
+ "readwrite",
1140
+ (store) => store.put({ key, bytes: stored, size: stored.byteLength })
1141
+ );
1142
+ }
1143
+ async clearCurrent(key) {
1144
+ await this.request("readwrite", (store) => store.delete(key));
1145
+ }
1146
+ async clearAll() {
1147
+ await this.request("readwrite", (store) => store.clear());
1148
+ }
1149
+ async estimate() {
1150
+ const database = await this.open();
1151
+ return await new Promise((resolve, reject) => {
1152
+ const transaction = database.transaction("models", "readonly");
1153
+ const request = transaction.objectStore("models").openCursor();
1154
+ let bytes = 0;
1155
+ let entries = 0;
1156
+ let cursorFinished = false;
1157
+ request.onerror = () => reject(request.error ?? new Error("\u8BFB\u53D6 IndexedDB \u7F13\u5B58\u5931\u8D25"));
1158
+ request.onsuccess = () => {
1159
+ const cursor = request.result;
1160
+ if (!cursor) {
1161
+ cursorFinished = true;
1162
+ return;
1163
+ }
1164
+ const value = cursor.value;
1165
+ bytes += typeof value.size === "number" ? value.size : value.bytes.byteLength;
1166
+ entries += 1;
1167
+ cursor.continue();
1168
+ };
1169
+ transaction.oncomplete = () => {
1170
+ if (cursorFinished) resolve({ bytes, entries });
1171
+ else reject(new Error("IndexedDB \u7F13\u5B58\u6E38\u6807\u672A\u5B8C\u6210"));
1172
+ };
1173
+ transaction.onabort = () => reject(transaction.error ?? new Error("IndexedDB \u7F13\u5B58\u4E8B\u52A1\u4E2D\u6B62"));
1174
+ });
1175
+ }
1176
+ async close() {
1177
+ if (!this.database) return;
1178
+ (await this.database).close();
1179
+ this.database = void 0;
1180
+ }
1181
+ open() {
1182
+ if (this.database) return this.database;
1183
+ this.database = new Promise((resolve, reject) => {
1184
+ const request = this.factory.open(this.databaseName, 1);
1185
+ request.onupgradeneeded = () => {
1186
+ if (!request.result.objectStoreNames.contains("models"))
1187
+ request.result.createObjectStore("models", { keyPath: "key" });
1188
+ };
1189
+ request.onerror = () => reject(request.error ?? new Error("\u6253\u5F00 IndexedDB \u7F13\u5B58\u5931\u8D25"));
1190
+ request.onsuccess = () => resolve(request.result);
1191
+ request.onblocked = () => reject(new Error("IndexedDB \u7F13\u5B58\u5347\u7EA7\u88AB\u963B\u585E"));
1192
+ });
1193
+ this.database.catch(() => {
1194
+ this.database = void 0;
1195
+ });
1196
+ return this.database;
1197
+ }
1198
+ async request(mode, operation) {
1199
+ const database = await this.open();
1200
+ return await new Promise((resolve, reject) => {
1201
+ const transaction = database.transaction("models", mode);
1202
+ const request = operation(transaction.objectStore("models"));
1203
+ let requestFinished = false;
1204
+ let result;
1205
+ request.onerror = () => reject(request.error ?? new Error("IndexedDB \u7F13\u5B58\u64CD\u4F5C\u5931\u8D25"));
1206
+ request.onsuccess = () => {
1207
+ requestFinished = true;
1208
+ result = request.result;
1209
+ };
1210
+ transaction.oncomplete = () => {
1211
+ if (requestFinished) resolve(result);
1212
+ else reject(new Error("IndexedDB \u7F13\u5B58\u8BF7\u6C42\u672A\u5B8C\u6210"));
1213
+ };
1214
+ transaction.onabort = () => reject(transaction.error ?? new Error("IndexedDB \u7F13\u5B58\u4E8B\u52A1\u4E2D\u6B62"));
1215
+ });
1216
+ }
1217
+ };
1218
+
1219
+ // src/cache/memory-cache.ts
1220
+ function clone(bytes) {
1221
+ return bytes.slice(0);
1222
+ }
1223
+ var MemoryModelCache = class {
1224
+ entries = /* @__PURE__ */ new Map();
1225
+ get(key) {
1226
+ const value = this.entries.get(key);
1227
+ return Promise.resolve(value ? clone(value) : void 0);
1228
+ }
1229
+ put(key, bytes) {
1230
+ this.entries.set(key, clone(bytes));
1231
+ return Promise.resolve();
1232
+ }
1233
+ clearCurrent(key) {
1234
+ this.entries.delete(key);
1235
+ return Promise.resolve();
1236
+ }
1237
+ clearAll() {
1238
+ this.entries.clear();
1239
+ return Promise.resolve();
1240
+ }
1241
+ estimate() {
1242
+ let bytes = 0;
1243
+ for (const value of this.entries.values()) bytes += value.byteLength;
1244
+ return Promise.resolve({ bytes, entries: this.entries.size });
1245
+ }
1246
+ close() {
1247
+ this.entries.clear();
1248
+ return Promise.resolve();
1249
+ }
1250
+ };
1251
+
1252
+ // src/cache/model-cache.ts
1253
+ var TieredModelCache = class {
1254
+ constructor(memory, persistent) {
1255
+ this.memory = memory;
1256
+ this.persistent = persistent;
1257
+ }
1258
+ memory;
1259
+ persistent;
1260
+ async get(key) {
1261
+ const memoryValue = await this.memory.get(key);
1262
+ if (memoryValue) return memoryValue;
1263
+ const persistentValue = await this.persistent?.get(key);
1264
+ if (persistentValue) await this.memory.put(key, persistentValue);
1265
+ return persistentValue;
1266
+ }
1267
+ async put(key, bytes) {
1268
+ await this.memory.put(key, bytes);
1269
+ await this.persistent?.put(key, bytes);
1270
+ }
1271
+ async clearCurrent(key) {
1272
+ await this.memory.clearCurrent(key);
1273
+ await this.persistent?.clearCurrent(key);
1274
+ }
1275
+ async clearAll() {
1276
+ await this.memory.clearAll();
1277
+ await this.persistent?.clearAll();
1278
+ }
1279
+ estimate() {
1280
+ return this.persistent?.estimate() ?? this.memory.estimate();
1281
+ }
1282
+ async close() {
1283
+ await this.memory.close?.();
1284
+ await this.persistent?.close?.();
1285
+ }
1286
+ };
1287
+
1288
+ // src/model/integrity.ts
1289
+ function abortIfNeeded(signal) {
1290
+ if (signal?.aborted) throw new PPDetectionError("ABORTED", "\u6A21\u578B\u5B8C\u6574\u6027\u6821\u9A8C\u5DF2\u53D6\u6D88");
1291
+ }
1292
+ async function calculateSha256(bytes, signal) {
1293
+ abortIfNeeded(signal);
1294
+ if (!globalThis.crypto?.subtle) {
1295
+ throw new PPDetectionError("MODEL_INTEGRITY_FAILED", "\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301 SHA-256 \u5B8C\u6574\u6027\u6821\u9A8C", {
1296
+ algorithm: "SHA-256"
1297
+ });
1298
+ }
1299
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
1300
+ abortIfNeeded(signal);
1301
+ return Array.from(new Uint8Array(digest), (value) => value.toString(16).padStart(2, "0")).join(
1302
+ ""
1303
+ );
1304
+ }
1305
+ async function verifyModelIntegrity(bytes, expected, signal) {
1306
+ abortIfNeeded(signal);
1307
+ if (bytes.byteLength !== expected.bytes) {
1308
+ throw new PPDetectionError("MODEL_INTEGRITY_FAILED", "\u6A21\u578B\u6587\u4EF6\u5927\u5C0F\u4E0E\u6E05\u5355\u4E0D\u4E00\u81F4", {
1309
+ expectedBytes: expected.bytes,
1310
+ actualBytes: bytes.byteLength
1311
+ });
1312
+ }
1313
+ const actualSha256 = await calculateSha256(bytes, signal);
1314
+ if (actualSha256 !== expected.sha256.toLowerCase()) {
1315
+ throw new PPDetectionError("MODEL_INTEGRITY_FAILED", "\u6A21\u578B SHA-256 \u4E0E\u6E05\u5355\u4E0D\u4E00\u81F4", {
1316
+ expectedSha256: expected.sha256.toLowerCase(),
1317
+ actualSha256
1318
+ });
1319
+ }
1320
+ }
1321
+
1322
+ // src/model/download.ts
1323
+ function now() {
1324
+ return globalThis.performance?.now() ?? Date.now();
1325
+ }
1326
+ function aborted(error, signal) {
1327
+ return signal?.aborted === true || error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
1328
+ }
1329
+ function throwIfAborted3(signal) {
1330
+ if (signal?.aborted) throw new PPDetectionError("ABORTED", "\u6A21\u578B\u4E0B\u8F7D\u5DF2\u53D6\u6D88");
1331
+ }
1332
+ function contentLength(response, expectedBytes) {
1333
+ const raw = response.headers.get("content-length");
1334
+ if (raw === null) return expectedBytes;
1335
+ const actual = Number(raw);
1336
+ if (!Number.isSafeInteger(actual) || actual < 0 || actual !== expectedBytes) {
1337
+ throw new PPDetectionError("MODEL_INTEGRITY_FAILED", "\u54CD\u5E94 Content-Length \u4E0E\u6E05\u5355\u4E0D\u4E00\u81F4", {
1338
+ contentLength: raw,
1339
+ expectedBytes
1340
+ });
1341
+ }
1342
+ return actual;
1343
+ }
1344
+ function validatePartialResponse(response, expectedBytes) {
1345
+ if (response.status !== 206) return;
1346
+ const raw = response.headers.get("content-range");
1347
+ const match = raw?.match(/^bytes (\d+)-(\d+)\/(\d+)$/i);
1348
+ if (!match || Number(match[1]) !== 0 || Number(match[2]) !== expectedBytes - 1 || Number(match[3]) !== expectedBytes) {
1349
+ throw new PPDetectionError("MODEL_DOWNLOAD_FAILED", "206 \u54CD\u5E94\u672A\u8986\u76D6\u5B8C\u6574\u6A21\u578B\u8303\u56F4", {
1350
+ contentRange: raw,
1351
+ expectedContentRange: `bytes 0-${expectedBytes - 1}/${expectedBytes}`
1352
+ });
1353
+ }
1354
+ }
1355
+ async function readResponse(response, expectedBytes, signal, onProgress) {
1356
+ const totalBytes = contentLength(response, expectedBytes);
1357
+ if (!response.body) {
1358
+ const bytes2 = await response.arrayBuffer();
1359
+ throwIfAborted3(signal);
1360
+ onProgress?.({ loadedBytes: bytes2.byteLength, totalBytes });
1361
+ return bytes2;
1362
+ }
1363
+ const reader = response.body.getReader();
1364
+ const chunks = [];
1365
+ let loadedBytes = 0;
1366
+ try {
1367
+ while (true) {
1368
+ throwIfAborted3(signal);
1369
+ const chunk = await reader.read();
1370
+ if (chunk.done) break;
1371
+ loadedBytes += chunk.value.byteLength;
1372
+ if (loadedBytes > expectedBytes) {
1373
+ throw new PPDetectionError("MODEL_INTEGRITY_FAILED", "\u6A21\u578B\u54CD\u5E94\u8D85\u8FC7\u6E05\u5355\u58F0\u660E\u5927\u5C0F", {
1374
+ expectedBytes,
1375
+ loadedBytes
1376
+ });
1377
+ }
1378
+ chunks.push(chunk.value);
1379
+ onProgress?.({ loadedBytes, totalBytes });
1380
+ }
1381
+ } catch (error) {
1382
+ try {
1383
+ await reader.cancel(error);
1384
+ } catch {
1385
+ }
1386
+ throw error;
1387
+ } finally {
1388
+ reader.releaseLock();
1389
+ }
1390
+ const bytes = new Uint8Array(loadedBytes);
1391
+ let offset = 0;
1392
+ for (const chunk of chunks) {
1393
+ bytes.set(chunk, offset);
1394
+ offset += chunk.byteLength;
1395
+ }
1396
+ onProgress?.({ loadedBytes, totalBytes });
1397
+ return bytes.buffer;
1398
+ }
1399
+ async function loadModelAsset(asset, options = {}) {
1400
+ throwIfAborted3(options.signal);
1401
+ const fetcher = options.fetcher ?? globalThis.fetch?.bind(globalThis);
1402
+ if (!fetcher)
1403
+ throw new PPDetectionError("MODEL_DOWNLOAD_FAILED", "\u5F53\u524D\u73AF\u5883\u6CA1\u6709 fetch API", {
1404
+ sourceKind: asset.source.kind
1405
+ });
1406
+ const downloadStarted = now();
1407
+ let response;
1408
+ try {
1409
+ response = await fetcher(asset.source.downloadUrl, { signal: options.signal });
1410
+ } catch (error) {
1411
+ if (aborted(error, options.signal))
1412
+ throw new PPDetectionError("ABORTED", "\u6A21\u578B\u4E0B\u8F7D\u5DF2\u53D6\u6D88", {}, { cause: error });
1413
+ throw new PPDetectionError(
1414
+ "MODEL_DOWNLOAD_FAILED",
1415
+ "\u6A21\u578B\u4E0B\u8F7D\u8BF7\u6C42\u5931\u8D25",
1416
+ {
1417
+ sourceKind: asset.source.kind,
1418
+ downloadUrl: asset.source.downloadUrl
1419
+ },
1420
+ { cause: error }
1421
+ );
1422
+ }
1423
+ if (!response.ok) {
1424
+ throw new PPDetectionError("MODEL_DOWNLOAD_FAILED", "\u6A21\u578B\u4E0B\u8F7D\u8FD4\u56DE\u975E\u6210\u529F\u72B6\u6001", {
1425
+ sourceKind: asset.source.kind,
1426
+ status: response.status,
1427
+ statusText: response.statusText
1428
+ });
1429
+ }
1430
+ validatePartialResponse(response, asset.source.bytes);
1431
+ let bytes;
1432
+ try {
1433
+ bytes = await readResponse(response, asset.source.bytes, options.signal, options.onProgress);
1434
+ } catch (error) {
1435
+ if (error instanceof PPDetectionError) throw error;
1436
+ if (aborted(error, options.signal))
1437
+ throw new PPDetectionError("ABORTED", "\u6A21\u578B\u4E0B\u8F7D\u5DF2\u53D6\u6D88", {}, { cause: error });
1438
+ throw new PPDetectionError(
1439
+ "MODEL_DOWNLOAD_FAILED",
1440
+ "\u8BFB\u53D6\u6A21\u578B\u54CD\u5E94\u5931\u8D25",
1441
+ { sourceKind: asset.source.kind },
1442
+ { cause: error }
1443
+ );
1444
+ }
1445
+ const modelDownloadMs = now() - downloadStarted;
1446
+ const integrityStarted = now();
1447
+ await verifyModelIntegrity(bytes, asset.source, options.signal);
1448
+ return {
1449
+ bytes,
1450
+ timings: {
1451
+ modelDownloadMs,
1452
+ integrityMs: now() - integrityStarted
1453
+ }
1454
+ };
1455
+ }
1456
+
1457
+ // src/model/source-resolver.ts
1458
+ function resolveModelVariant(manifest, variantId) {
1459
+ const variant3 = variantId === void 0 ? manifest.variants[0] : manifest.variants.find((candidate) => candidate.id === variantId);
1460
+ if (!variant3) {
1461
+ throw new PPDetectionError("MODEL_INCOMPATIBLE", "\u8BF7\u6C42\u7684\u6A21\u578B\u53D8\u4F53\u4E0D\u5B58\u5728", {
1462
+ variantId,
1463
+ availableVariants: manifest.variants.map((candidate) => candidate.id)
1464
+ });
1465
+ }
1466
+ return variant3;
1467
+ }
1468
+ function resolveModelSources(variant3, sourceKind2 = "auto") {
1469
+ if (sourceKind2 === "auto") return variant3.sources;
1470
+ const source2 = variant3.sources.find((candidate) => candidate.kind === sourceKind2);
1471
+ if (!source2) {
1472
+ throw new PPDetectionError("MODEL_SOURCE_UNAVAILABLE", "\u8BF7\u6C42\u7684\u6A21\u578B\u6765\u6E90\u4E0D\u5B58\u5728", {
1473
+ sourceKind: sourceKind2,
1474
+ availableSources: variant3.sources.map((candidate) => candidate.kind)
1475
+ });
1476
+ }
1477
+ return [source2];
1478
+ }
1479
+ function resolveModelAsset(selection, manifest) {
1480
+ const variant3 = resolveModelVariant(manifest, selection.variantId);
1481
+ const [source2] = resolveModelSources(variant3, selection.sourceKind);
1482
+ if (!source2)
1483
+ throw new PPDetectionError("MODEL_SOURCE_UNAVAILABLE", "\u6A21\u578B\u53D8\u4F53\u6CA1\u6709\u53EF\u7528\u6765\u6E90", {
1484
+ variantId: variant3.id
1485
+ });
1486
+ return { model: manifest.model, variant: variant3, source: source2 };
1487
+ }
1488
+
1489
+ // src/model/model-manager.ts
1490
+ var SDK_CACHE_NAMESPACE = "web-sdk-pp-detection:cache-v1";
1491
+ function now2() {
1492
+ return globalThis.performance?.now() ?? Date.now();
1493
+ }
1494
+ function createCache(selection) {
1495
+ if (selection && typeof selection === "object") return selection;
1496
+ const memory = new MemoryModelCache();
1497
+ if (selection === false || selection === "memory" || typeof globalThis.indexedDB === "undefined")
1498
+ return memory;
1499
+ return new TieredModelCache(memory, new IndexedDBModelCache());
1500
+ }
1501
+ function sourceFailure(source2, error) {
1502
+ return {
1503
+ kind: source2.kind,
1504
+ code: error instanceof PPDetectionError ? error.code : "MODEL_DOWNLOAD_FAILED",
1505
+ message: error instanceof Error ? error.message : String(error)
1506
+ };
1507
+ }
1508
+ function throwIfAborted4(signal) {
1509
+ if (signal?.aborted) throw new PPDetectionError("ABORTED", "\u6A21\u578B\u52A0\u8F7D\u5DF2\u53D6\u6D88");
1510
+ }
1511
+ var ModelManager = class {
1512
+ fetcher;
1513
+ cache;
1514
+ lifecycle = new AbortController();
1515
+ activeLoads = /* @__PURE__ */ new Set();
1516
+ currentKey;
1517
+ disposed = false;
1518
+ disposePromise;
1519
+ constructor(options = {}) {
1520
+ this.fetcher = options.fetcher;
1521
+ this.cache = createCache(options.cache);
1522
+ }
1523
+ cacheKey(variant3, source2, model = { id: "unknown", version: "unknown" }) {
1524
+ return JSON.stringify([
1525
+ SDK_CACHE_NAMESPACE,
1526
+ model.id,
1527
+ model.version,
1528
+ variant3.id,
1529
+ source2.revision.toLowerCase(),
1530
+ source2.sha256.toLowerCase()
1531
+ ]);
1532
+ }
1533
+ async load(options) {
1534
+ if (this.disposed) throw new PPDetectionError("DISPOSED", "\u6A21\u578B\u7BA1\u7406\u5668\u5DF2\u91CA\u653E");
1535
+ const controller = new AbortController();
1536
+ const abort = () => controller.abort();
1537
+ options.signal?.addEventListener("abort", abort, { once: true });
1538
+ this.lifecycle.signal.addEventListener("abort", abort, { once: true });
1539
+ if (options.signal?.aborted || this.lifecycle.signal.aborted) abort();
1540
+ const operation = this.loadActive({ ...options, signal: controller.signal });
1541
+ const tracked = operation.then(
1542
+ () => void 0,
1543
+ () => void 0
1544
+ );
1545
+ this.activeLoads.add(tracked);
1546
+ try {
1547
+ return await operation;
1548
+ } finally {
1549
+ options.signal?.removeEventListener("abort", abort);
1550
+ this.lifecycle.signal.removeEventListener("abort", abort);
1551
+ this.activeLoads.delete(tracked);
1552
+ }
1553
+ }
1554
+ async loadActive(options) {
1555
+ throwIfAborted4(options.signal);
1556
+ const manifest = parseDetectionManifest(options.manifest);
1557
+ const variant3 = resolveModelVariant(manifest, options.variantId);
1558
+ const sourceKind2 = options.sourceKind ?? "auto";
1559
+ const sources = resolveModelSources(variant3, sourceKind2);
1560
+ const failures = [];
1561
+ let lastError;
1562
+ for (const source2 of sources) {
1563
+ throwIfAborted4(options.signal);
1564
+ const asset = { model: manifest.model, variant: variant3, source: source2 };
1565
+ const cacheKey = this.cacheKey(variant3, source2, manifest.model);
1566
+ const cacheStarted = now2();
1567
+ let cached;
1568
+ try {
1569
+ cached = await this.cache.get(cacheKey);
1570
+ } catch (error) {
1571
+ if (error instanceof PPDetectionError && error.code === "ABORTED") throw error;
1572
+ throwIfAborted4(options.signal);
1573
+ }
1574
+ throwIfAborted4(options.signal);
1575
+ const modelCacheReadMs = now2() - cacheStarted;
1576
+ if (cached) {
1577
+ const integrityStarted = now2();
1578
+ try {
1579
+ await verifyModelIntegrity(cached, source2, options.signal);
1580
+ this.currentKey = cacheKey;
1581
+ return {
1582
+ bytes: cached,
1583
+ manifest,
1584
+ variant: variant3,
1585
+ source: source2,
1586
+ cacheKey,
1587
+ fromCache: true,
1588
+ failures,
1589
+ timings: { modelCacheReadMs, integrityMs: now2() - integrityStarted }
1590
+ };
1591
+ } catch (error) {
1592
+ if (error instanceof PPDetectionError && error.code === "ABORTED") throw error;
1593
+ try {
1594
+ await this.cache.clearCurrent(cacheKey);
1595
+ } catch (clearError) {
1596
+ if (clearError instanceof PPDetectionError && clearError.code === "ABORTED")
1597
+ throw clearError;
1598
+ throwIfAborted4(options.signal);
1599
+ }
1600
+ }
1601
+ }
1602
+ let loaded;
1603
+ try {
1604
+ loaded = await loadModelAsset(asset, {
1605
+ fetcher: this.fetcher,
1606
+ signal: options.signal,
1607
+ onProgress: options.onProgress
1608
+ });
1609
+ } catch (error) {
1610
+ if (error instanceof PPDetectionError && error.code === "ABORTED") throw error;
1611
+ lastError = error;
1612
+ failures.push(sourceFailure(source2, error));
1613
+ if (sourceKind2 !== "auto") {
1614
+ if (error instanceof PPDetectionError && error.code === "MODEL_INTEGRITY_FAILED")
1615
+ throw error;
1616
+ throw new PPDetectionError(
1617
+ "MODEL_SOURCE_UNAVAILABLE",
1618
+ "\u8BF7\u6C42\u7684\u6A21\u578B\u6765\u6E90\u4E0D\u53EF\u7528",
1619
+ {
1620
+ sourceKind: source2.kind,
1621
+ failures
1622
+ },
1623
+ { cause: error }
1624
+ );
1625
+ }
1626
+ continue;
1627
+ }
1628
+ throwIfAborted4(options.signal);
1629
+ try {
1630
+ await this.cache.put(cacheKey, loaded.bytes);
1631
+ } catch {
1632
+ }
1633
+ throwIfAborted4(options.signal);
1634
+ this.currentKey = cacheKey;
1635
+ return {
1636
+ bytes: loaded.bytes,
1637
+ manifest,
1638
+ variant: variant3,
1639
+ source: source2,
1640
+ cacheKey,
1641
+ fromCache: false,
1642
+ failures,
1643
+ timings: { modelCacheReadMs, ...loaded.timings }
1644
+ };
1645
+ }
1646
+ throw new PPDetectionError(
1647
+ "MODEL_SOURCE_UNAVAILABLE",
1648
+ "\u6240\u6709\u6A21\u578B\u6765\u6E90\u5747\u4E0D\u53EF\u7528",
1649
+ {
1650
+ variantId: variant3.id,
1651
+ failures
1652
+ },
1653
+ { cause: lastError }
1654
+ );
1655
+ }
1656
+ estimate() {
1657
+ return this.getCacheEstimate();
1658
+ }
1659
+ getCacheEstimate() {
1660
+ return this.cache.estimate();
1661
+ }
1662
+ async clearCurrentModelCache() {
1663
+ if (!this.currentKey) return;
1664
+ await this.cache.clearCurrent(this.currentKey);
1665
+ }
1666
+ async clearAllCache() {
1667
+ await this.cache.clearAll();
1668
+ this.currentKey = void 0;
1669
+ }
1670
+ async dispose() {
1671
+ if (this.disposePromise) return await this.disposePromise;
1672
+ this.disposed = true;
1673
+ this.lifecycle.abort();
1674
+ this.disposePromise = (async () => {
1675
+ await Promise.all([...this.activeLoads]);
1676
+ this.currentKey = void 0;
1677
+ await this.cache.close?.();
1678
+ })();
1679
+ await this.disposePromise;
1680
+ }
1681
+ };
1682
+
1683
+ // src/runtime/ort-session.ts
1684
+ function now3() {
1685
+ return globalThis.performance?.now() ?? Date.now();
1686
+ }
1687
+ function mapError(error, phase) {
1688
+ if (error instanceof PPDetectionError) return error;
1689
+ const message = error instanceof Error ? error.message : String(error);
1690
+ if (/abort|cancel/i.test(message))
1691
+ return new PPDetectionError("ABORTED", "\u63A8\u7406\u5DF2\u53D6\u6D88", { phase }, { cause: error });
1692
+ if (/memory|out.of.memory|allocation/i.test(message))
1693
+ return new PPDetectionError("OUT_OF_MEMORY", "\u8FD0\u884C\u65F6\u5185\u5B58\u4E0D\u8DB3", { phase }, { cause: error });
1694
+ return new PPDetectionError(
1695
+ phase === "create" ? "SESSION_CREATE_FAILED" : "INFERENCE_FAILED",
1696
+ phase === "create" ? "\u521B\u5EFA ONNX Runtime \u4F1A\u8BDD\u5931\u8D25" : "ONNX Runtime \u63A8\u7406\u5931\u8D25",
1697
+ { phase },
1698
+ { cause: error }
1699
+ );
1700
+ }
1701
+ function normalizeFeeds(feeds, ort) {
1702
+ const Tensor = ort.Tensor;
1703
+ if (!Tensor) return feeds;
1704
+ return Object.fromEntries(
1705
+ Object.entries(feeds).map(([name, value]) => {
1706
+ if (typeof value === "object" && value !== null && "data" in value && "dims" in value && value.data instanceof Float32Array) {
1707
+ const input = value;
1708
+ return [name, new Tensor("float32", input.data, input.dims)];
1709
+ }
1710
+ return [name, value];
1711
+ })
1712
+ );
1713
+ }
1714
+ async function loadOrt(backend) {
1715
+ return await (backend === "webgpu" ? import('onnxruntime-web/webgpu') : import('onnxruntime-web'));
1716
+ }
1717
+ async function createOrtSession(modelBytes, plan, options = {}) {
1718
+ try {
1719
+ const ort = options.ort ?? await (options.loadOrt ?? loadOrt)(plan.actualBackend);
1720
+ if (options.wasmPaths && ort.env.wasm) ort.env.wasm.wasmPaths = options.wasmPaths;
1721
+ if (plan.actualBackend === "wasm" && ort.env.wasm && options.numThreads)
1722
+ ort.env.wasm.numThreads = options.numThreads;
1723
+ const sessionStarted = now3();
1724
+ const session = await ort.InferenceSession.create(modelBytes, {
1725
+ ...options.sessionOptions,
1726
+ executionProviders: [plan.actualBackend]
1727
+ });
1728
+ const sessionMs = now3() - sessionStarted;
1729
+ let disposed = false;
1730
+ let disposePromise;
1731
+ const activeRuns = /* @__PURE__ */ new Set();
1732
+ return {
1733
+ plan,
1734
+ sessionMs,
1735
+ async run(feeds, runOptions = {}) {
1736
+ if (disposed) throw new PPDetectionError("DISPOSED", "\u4F1A\u8BDD\u5DF2\u91CA\u653E", { phase: "run" });
1737
+ if (runOptions.signal?.aborted)
1738
+ throw new PPDetectionError("ABORTED", "\u63A8\u7406\u5DF2\u53D6\u6D88", { phase: "run" });
1739
+ const ortRunOptions = {};
1740
+ if (plan.actualBackend === "wasm") ortRunOptions.terminate = false;
1741
+ let cancelled = false;
1742
+ const onAbort = () => {
1743
+ cancelled = true;
1744
+ };
1745
+ runOptions.signal?.addEventListener("abort", onAbort, { once: true });
1746
+ const operation = (async () => {
1747
+ try {
1748
+ const result = await Promise.resolve(
1749
+ session.run(normalizeFeeds(feeds, ort), ortRunOptions)
1750
+ );
1751
+ if (cancelled) throw new PPDetectionError("ABORTED", "\u63A8\u7406\u5DF2\u53D6\u6D88", { phase: "run" });
1752
+ return result;
1753
+ } catch (error) {
1754
+ if (cancelled)
1755
+ throw new PPDetectionError(
1756
+ "ABORTED",
1757
+ "\u63A8\u7406\u5DF2\u53D6\u6D88",
1758
+ { phase: "run" },
1759
+ { cause: error }
1760
+ );
1761
+ throw mapError(error, "run");
1762
+ } finally {
1763
+ runOptions.signal?.removeEventListener("abort", onAbort);
1764
+ }
1765
+ })();
1766
+ const tracked = operation.then(
1767
+ () => void 0,
1768
+ () => void 0
1769
+ );
1770
+ activeRuns.add(tracked);
1771
+ try {
1772
+ return await operation;
1773
+ } finally {
1774
+ activeRuns.delete(tracked);
1775
+ }
1776
+ },
1777
+ async dispose() {
1778
+ if (disposePromise) return await disposePromise;
1779
+ disposed = true;
1780
+ disposePromise = (async () => {
1781
+ await Promise.all([...activeRuns]);
1782
+ await session.release?.();
1783
+ })();
1784
+ await disposePromise;
1785
+ }
1786
+ };
1787
+ } catch (error) {
1788
+ throw mapError(error, "create");
1789
+ }
1790
+ }
1791
+
1792
+ // src/runtime/capabilities.ts
1793
+ var WASM_SIMD_PROBE = new Uint8Array([
1794
+ 0,
1795
+ 97,
1796
+ 115,
1797
+ 109,
1798
+ 1,
1799
+ 0,
1800
+ 0,
1801
+ 0,
1802
+ 1,
1803
+ 5,
1804
+ 1,
1805
+ 96,
1806
+ 0,
1807
+ 1,
1808
+ 123,
1809
+ 3,
1810
+ 2,
1811
+ 1,
1812
+ 0,
1813
+ 10,
1814
+ 10,
1815
+ 1,
1816
+ 8,
1817
+ 0,
1818
+ 253,
1819
+ 15,
1820
+ 0,
1821
+ 0,
1822
+ 0,
1823
+ 11
1824
+ ]);
1825
+ function hasWasmSimd(scope) {
1826
+ try {
1827
+ return typeof scope.WebAssembly !== "undefined" && scope.WebAssembly.validate(WASM_SIMD_PROBE);
1828
+ } catch {
1829
+ return false;
1830
+ }
1831
+ }
1832
+ function hasWasmThreads(scope) {
1833
+ try {
1834
+ return typeof scope.SharedArrayBuffer !== "undefined" && typeof scope.Atomics !== "undefined" && scope.crossOriginIsolated === true;
1835
+ } catch {
1836
+ return false;
1837
+ }
1838
+ }
1839
+ function probeCapabilities(options = {}) {
1840
+ const scope = options.global ?? globalThis;
1841
+ const navigatorValue = scope.navigator;
1842
+ return {
1843
+ webgpu: Boolean(navigatorValue && "gpu" in navigatorValue && navigatorValue.gpu),
1844
+ worker: typeof scope.Worker === "function",
1845
+ offscreenCanvas: typeof scope.OffscreenCanvas === "function",
1846
+ wasmSimd: hasWasmSimd(scope),
1847
+ wasmThreads: hasWasmThreads(scope)
1848
+ };
1849
+ }
1850
+
1851
+ // src/runtime/select-plan.ts
1852
+ function fail(code, message, details) {
1853
+ throw new PPDetectionError(code, message, details);
1854
+ }
1855
+ function candidatesForBackend(requested, capabilities, allowFallback) {
1856
+ if (requested === "wasm") return ["wasm"];
1857
+ if (requested === "webgpu")
1858
+ return capabilities.webgpu ? ["webgpu"] : fail("CAPABILITY_UNSUPPORTED", "\u8BF7\u6C42\u7684 webgpu \u4E0D\u53EF\u7528", { requestedBackend: requested });
1859
+ const available = [];
1860
+ if (capabilities.webgpu) available.push("webgpu");
1861
+ available.push("wasm");
1862
+ if (!allowFallback) return available.slice(0, 1);
1863
+ return available;
1864
+ }
1865
+ function selectExecutionPlan(options, capabilities, manifest) {
1866
+ const requestedBackend = options.backend ?? "auto";
1867
+ const requestedPrecision = options.precision ?? "fp32";
1868
+ const executionMode = options.executionMode ?? "main";
1869
+ if (executionMode === "worker" && !capabilities.worker) {
1870
+ fail("CAPABILITY_UNSUPPORTED", "\u8BF7\u6C42\u7684 worker \u4E0D\u53EF\u7528", { executionMode });
1871
+ }
1872
+ const variant3 = manifest.variants?.find(
1873
+ (candidate) => candidate.precision === requestedPrecision && candidate.status !== "labs" && candidate.status !== "blocked"
1874
+ );
1875
+ if (!variant3) {
1876
+ fail("MODEL_INCOMPATIBLE", `manifest \u6CA1\u6709\u53EF\u7528\u7684 ${requestedPrecision} \u7A33\u5B9A\u53D8\u4F53`, {
1877
+ requestedPrecision
1878
+ });
1879
+ }
1880
+ const candidates = candidatesForBackend(
1881
+ requestedBackend,
1882
+ capabilities,
1883
+ options.allowFallback === true
1884
+ ).filter((backend) => variant3.backends.includes(backend));
1885
+ if (candidates.length === 0) {
1886
+ fail("CAPABILITY_UNSUPPORTED", "\u6CA1\u6709\u4E0E\u6A21\u578B\u53D8\u4F53\u5339\u914D\u7684\u53EF\u7528\u540E\u7AEF", {
1887
+ requestedBackend,
1888
+ requestedPrecision,
1889
+ availableBackends: variant3.backends
1890
+ });
1891
+ }
1892
+ const actualBackend = candidates[0];
1893
+ return {
1894
+ variantId: variant3.id,
1895
+ requestedBackend,
1896
+ actualBackend,
1897
+ requestedPrecision,
1898
+ actualPrecision: variant3.precision,
1899
+ executionMode,
1900
+ candidates: candidates.map((backend) => ({
1901
+ variantId: variant3.id,
1902
+ backend,
1903
+ precision: variant3.precision,
1904
+ executionMode
1905
+ }))
1906
+ };
1907
+ }
1908
+
1909
+ // src/runtime/protocol.ts
1910
+ function transferableValues(value) {
1911
+ const transferables = /* @__PURE__ */ new Set();
1912
+ const seen = /* @__PURE__ */ new Set();
1913
+ const visit = (candidate) => {
1914
+ if (!candidate || typeof candidate !== "object") return;
1915
+ if (seen.has(candidate)) return;
1916
+ seen.add(candidate);
1917
+ if (candidate instanceof ArrayBuffer) {
1918
+ transferables.add(candidate);
1919
+ return;
1920
+ }
1921
+ if (ArrayBuffer.isView(candidate)) {
1922
+ if (candidate.buffer instanceof ArrayBuffer) transferables.add(candidate.buffer);
1923
+ return;
1924
+ }
1925
+ for (const child of Object.values(candidate)) visit(child);
1926
+ };
1927
+ visit(value);
1928
+ return [...transferables];
1929
+ }
1930
+
1931
+ // src/runtime/worker-bridge.ts
1932
+ var WorkerBridge = class {
1933
+ constructor(worker) {
1934
+ this.worker = worker;
1935
+ worker.onmessage = (event) => this.handleResponse(event.data);
1936
+ worker.onerror = (event) => this.shutdown(
1937
+ new PPDetectionError("INFERENCE_FAILED", event.message || "Worker \u6267\u884C\u5931\u8D25", {
1938
+ source: "worker"
1939
+ })
1940
+ );
1941
+ }
1942
+ worker;
1943
+ pending = /* @__PURE__ */ new Map();
1944
+ state = "active";
1945
+ disposePromise;
1946
+ terminated = false;
1947
+ sequence = 0;
1948
+ load(modelBytes, plan, options = {}) {
1949
+ return this.request(
1950
+ { type: "load", modelBytes, plan, ...options.ort ? { ort: options.ort } : {} },
1951
+ [modelBytes],
1952
+ options
1953
+ );
1954
+ }
1955
+ run(input, options = {}) {
1956
+ return this.request({ type: "run", input }, transferableValues(input), {
1957
+ signal: options.signal,
1958
+ onProgress: options.onProgress
1959
+ });
1960
+ }
1961
+ dispose() {
1962
+ if (this.disposePromise) return this.disposePromise;
1963
+ if (this.state === "disposed") return Promise.resolve();
1964
+ this.state = "disposing";
1965
+ this.disposePromise = this.request({ type: "dispose" }, [], { allowDuringDispose: true }).then(() => void 0).finally(() => this.shutdown(new PPDetectionError("DISPOSED", "Worker \u5DF2\u91CA\u653E")));
1966
+ return this.disposePromise;
1967
+ }
1968
+ request(payload, transfer, options = {}) {
1969
+ if (this.state !== "active" && !(options.allowDuringDispose && this.state === "disposing")) {
1970
+ return Promise.reject(new PPDetectionError("DISPOSED", "Worker \u5DF2\u91CA\u653E"));
1971
+ }
1972
+ if (options.signal?.aborted)
1973
+ return Promise.reject(new PPDetectionError("ABORTED", "Worker \u63A8\u7406\u5DF2\u53D6\u6D88"));
1974
+ const id = String(++this.sequence);
1975
+ return new Promise((resolve, reject) => {
1976
+ let posted = false;
1977
+ const cleanup = () => options.signal?.removeEventListener("abort", onAbort);
1978
+ const onAbort = () => {
1979
+ if (!this.pending.delete(id)) return;
1980
+ cleanup();
1981
+ if (posted && payload.type === "run") this.sendCancel(id);
1982
+ reject(new PPDetectionError("ABORTED", "Worker \u63A8\u7406\u5DF2\u53D6\u6D88"));
1983
+ };
1984
+ this.pending.set(id, { resolve, reject, cleanup, onProgress: options.onProgress });
1985
+ options.signal?.addEventListener("abort", onAbort, { once: true });
1986
+ if (options.signal?.aborted) {
1987
+ onAbort();
1988
+ return;
1989
+ }
1990
+ try {
1991
+ this.worker.postMessage({ id, ...payload }, transfer);
1992
+ posted = true;
1993
+ } catch (error) {
1994
+ this.pending.delete(id);
1995
+ cleanup();
1996
+ reject(
1997
+ new PPDetectionError(
1998
+ "INFERENCE_FAILED",
1999
+ "\u5411 Worker \u53D1\u9001\u6D88\u606F\u5931\u8D25",
2000
+ { requestType: payload.type },
2001
+ { cause: error }
2002
+ )
2003
+ );
2004
+ }
2005
+ });
2006
+ }
2007
+ sendCancel(requestId) {
2008
+ try {
2009
+ this.worker.postMessage({ id: String(++this.sequence), type: "cancel", requestId }, []);
2010
+ } catch {
2011
+ }
2012
+ }
2013
+ handleResponse(response) {
2014
+ if (response.type === "progress") {
2015
+ this.pending.get(response.id)?.onProgress?.({
2016
+ phase: response.phase,
2017
+ status: response.status,
2018
+ ...response.loadedBytes === void 0 ? {} : { loadedBytes: response.loadedBytes },
2019
+ ...response.totalBytes === void 0 ? {} : { totalBytes: response.totalBytes }
2020
+ });
2021
+ return;
2022
+ }
2023
+ const request = this.pending.get(response.id);
2024
+ if (!request) return;
2025
+ this.pending.delete(response.id);
2026
+ request.cleanup();
2027
+ if (response.type === "result") request.resolve(response.result);
2028
+ else
2029
+ request.reject(
2030
+ new PPDetectionError(
2031
+ response.error.code,
2032
+ response.error.message,
2033
+ response.error.details ?? {}
2034
+ )
2035
+ );
2036
+ }
2037
+ failAll(error) {
2038
+ for (const request of this.pending.values()) {
2039
+ request.cleanup();
2040
+ request.reject(error);
2041
+ }
2042
+ this.pending.clear();
2043
+ }
2044
+ shutdown(error) {
2045
+ this.state = "disposed";
2046
+ if (!this.terminated) {
2047
+ this.terminated = true;
2048
+ this.worker.terminate();
2049
+ }
2050
+ this.failAll(error);
2051
+ }
2052
+ };
2053
+
2054
+ // src/index.ts
2055
+ var CURRENT_SDK_VERSION = "0.1.0";
2056
+ function probePPDetectionCapabilities(options = {}) {
2057
+ return probeCapabilities(options);
2058
+ }
2059
+ function now4() {
2060
+ return globalThis.performance?.now() ?? Date.now();
2061
+ }
2062
+ function isRuntimeManifest(value) {
2063
+ if (typeof value !== "object" || value === null || !("postprocessing" in value)) return false;
2064
+ const variants = value.variants;
2065
+ return Array.isArray(variants) && variants[0]?.sources !== void 0;
2066
+ }
2067
+ function isModelData(value) {
2068
+ return typeof value === "object" && value !== null && "data" in value && "manifest" in value;
2069
+ }
2070
+ async function fetchJson(url2, signal) {
2071
+ let response;
2072
+ try {
2073
+ response = await fetch(url2, { signal });
2074
+ } catch (error) {
2075
+ if (signal?.aborted || error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError") {
2076
+ throw new PPDetectionError("ABORTED", "\u6A21\u578B\u6E05\u5355\u4E0B\u8F7D\u5DF2\u53D6\u6D88", { url: url2 }, { cause: error });
2077
+ }
2078
+ throw new PPDetectionError(
2079
+ "MODEL_SOURCE_UNAVAILABLE",
2080
+ "\u6A21\u578B\u6E05\u5355\u4E0B\u8F7D\u5931\u8D25",
2081
+ { url: url2 },
2082
+ { cause: error }
2083
+ );
2084
+ }
2085
+ if (signal?.aborted) throw new PPDetectionError("ABORTED", "\u6A21\u578B\u6E05\u5355\u4E0B\u8F7D\u5DF2\u53D6\u6D88", { url: url2 });
2086
+ if (!response.ok)
2087
+ throw new PPDetectionError("MODEL_SOURCE_UNAVAILABLE", "\u6A21\u578B\u6E05\u5355\u8FD4\u56DE\u975E\u6210\u529F\u72B6\u6001", {
2088
+ url: url2,
2089
+ status: response.status
2090
+ });
2091
+ try {
2092
+ const value = await response.json();
2093
+ if (signal?.aborted) throw new PPDetectionError("ABORTED", "\u6A21\u578B\u6E05\u5355\u4E0B\u8F7D\u5DF2\u53D6\u6D88", { url: url2 });
2094
+ return value;
2095
+ } catch (error) {
2096
+ if (error instanceof PPDetectionError) throw error;
2097
+ if (signal?.aborted || error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError") {
2098
+ throw new PPDetectionError("ABORTED", "\u6A21\u578B\u6E05\u5355\u4E0B\u8F7D\u5DF2\u53D6\u6D88", { url: url2 }, { cause: error });
2099
+ }
2100
+ throw new PPDetectionError(
2101
+ "INVALID_MANIFEST",
2102
+ "\u6A21\u578B\u6E05\u5355 JSON \u65E0\u6CD5\u89E3\u6790",
2103
+ { url: url2 },
2104
+ { cause: error }
2105
+ );
2106
+ }
2107
+ }
2108
+ function asRuntimeManifest(value) {
2109
+ return isRuntimeManifest(value) ? parseDetectionManifest(value) : adaptModelManifest(parseModelManifest(value));
2110
+ }
2111
+ function simpleManifest(runtime) {
2112
+ return {
2113
+ id: runtime.model.id,
2114
+ version: runtime.model.version,
2115
+ variants: runtime.variants.map((variant3) => ({
2116
+ id: variant3.id,
2117
+ precision: variant3.precision,
2118
+ quantization: variant3.quantization,
2119
+ backends: variant3.backends,
2120
+ status: variant3.status
2121
+ }))
2122
+ };
2123
+ }
2124
+ function modelInfo(runtime, variantId, source2) {
2125
+ const variant3 = runtime.variants.find((candidate) => candidate.id === variantId);
2126
+ if (!variant3)
2127
+ throw new PPDetectionError("MODEL_INCOMPATIBLE", "\u8BF7\u6C42\u7684\u6A21\u578B\u53D8\u4F53\u4E0D\u5B58\u5728", { variantId });
2128
+ return {
2129
+ id: runtime.model.id,
2130
+ version: runtime.model.version,
2131
+ variantId: variant3.id,
2132
+ precision: variant3.precision,
2133
+ bytes: variant3.bytes,
2134
+ parameterCount: variant3.parameterCount,
2135
+ opset: variant3.opset,
2136
+ source: {
2137
+ kind: source2.kind,
2138
+ revision: source2.revision,
2139
+ bytes: source2.bytes,
2140
+ sha256: source2.sha256
2141
+ }
2142
+ };
2143
+ }
2144
+ function workerUrl() {
2145
+ const scriptUrl = globalThis.__PPDETECTION_SCRIPT_URL__;
2146
+ if (scriptUrl) return new URL("./inference.worker.js", scriptUrl);
2147
+ return new URL("./inference.worker.js", import.meta.url);
2148
+ }
2149
+ async function createPPDetection(options = {}) {
2150
+ if (options.model === void 0 && options.manifest === void 0)
2151
+ throw new PPDetectionError("INVALID_MANIFEST", "\u521B\u5EFA PPDetection \u5B9E\u4F8B\u9700\u8981 manifest \u6216 model");
2152
+ const capabilities = probeCapabilities();
2153
+ options.onProgress?.({ phase: "capabilities", status: "complete" });
2154
+ const requestedModel = options.model ?? options.manifest;
2155
+ let runtimeManifest;
2156
+ let memoryData;
2157
+ options.onProgress?.({ phase: "manifest", status: "start" });
2158
+ if (typeof requestedModel === "string") {
2159
+ runtimeManifest = asRuntimeManifest(await fetchJson(requestedModel, options.signal));
2160
+ } else if (isModelData(requestedModel)) {
2161
+ runtimeManifest = asRuntimeManifest(requestedModel.manifest);
2162
+ memoryData = requestedModel.data;
2163
+ } else {
2164
+ runtimeManifest = asRuntimeManifest(requestedModel);
2165
+ }
2166
+ options.onProgress?.({ phase: "manifest", status: "complete" });
2167
+ const plan = selectExecutionPlan(
2168
+ {
2169
+ backend: options.backend,
2170
+ precision: options.precision === "auto" ? void 0 : options.precision,
2171
+ executionMode: options.executionMode,
2172
+ allowFallback: options.allowFallback
2173
+ },
2174
+ capabilities,
2175
+ simpleManifest(runtimeManifest)
2176
+ );
2177
+ const modelManager = new ModelManager({
2178
+ cache: options.cache === false ? false : options.cache === "memory" ? "memory" : void 0
2179
+ });
2180
+ let executor;
2181
+ let activeBridge;
2182
+ try {
2183
+ const loadStartedAt = now4();
2184
+ options.onProgress?.({ phase: "model", status: "start" });
2185
+ let modelBytes;
2186
+ let actualSource;
2187
+ let variant3 = runtimeManifest.variants.find((candidate) => candidate.id === plan.variantId);
2188
+ let loadTimings;
2189
+ if (memoryData !== void 0) {
2190
+ const [source2] = resolveModelSources(variant3, options.source ?? "auto");
2191
+ if (!source2)
2192
+ throw new PPDetectionError("MODEL_SOURCE_UNAVAILABLE", "\u6A21\u578B\u53D8\u4F53\u6CA1\u6709\u53EF\u7528\u6765\u6E90", {
2193
+ variantId: variant3.id
2194
+ });
2195
+ await verifyModelIntegrity(memoryData, source2, options.signal);
2196
+ modelBytes = memoryData;
2197
+ actualSource = source2;
2198
+ loadTimings = { sessionMs: 0, totalMs: now4() - loadStartedAt, integrityMs: 0 };
2199
+ } else {
2200
+ const loaded = await modelManager.load({
2201
+ manifest: runtimeManifest,
2202
+ variantId: plan.variantId,
2203
+ sourceKind: options.source ?? "auto",
2204
+ signal: options.signal,
2205
+ onProgress: (progress) => options.onProgress?.({ phase: "model", status: "progress", ...progress })
2206
+ });
2207
+ modelBytes = loaded.bytes;
2208
+ variant3 = loaded.variant;
2209
+ actualSource = loaded.source;
2210
+ loadTimings = { ...loaded.timings, sessionMs: 0, totalMs: now4() - loadStartedAt };
2211
+ }
2212
+ options.onProgress?.({ phase: "model", status: "complete" });
2213
+ const fallbacks = [];
2214
+ let selectedPlan = plan;
2215
+ let sessionMs = 0;
2216
+ for (const candidate of plan.candidates) {
2217
+ const candidatePlan = {
2218
+ ...plan,
2219
+ variantId: candidate.variantId,
2220
+ actualBackend: candidate.backend,
2221
+ actualPrecision: candidate.precision,
2222
+ executionMode: candidate.executionMode,
2223
+ candidates: [candidate]
2224
+ };
2225
+ options.onProgress?.({ phase: "session", status: "start" });
2226
+ try {
2227
+ if (candidate.executionMode === "worker") {
2228
+ if (typeof Worker !== "function")
2229
+ throw new PPDetectionError("CAPABILITY_UNSUPPORTED", "\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301 Worker");
2230
+ const worker = new Worker(workerUrl(), {
2231
+ type: "module"
2232
+ });
2233
+ const bridge = new WorkerBridge(worker);
2234
+ activeBridge = bridge;
2235
+ const workerModelBytes = modelBytes.slice(0);
2236
+ await bridge.load(workerModelBytes, candidatePlan, {
2237
+ onProgress: (event) => options.onProgress?.({
2238
+ phase: "session",
2239
+ status: event.status
2240
+ }),
2241
+ ort: {
2242
+ wasmPaths: options.ort?.wasm?.paths,
2243
+ numThreads: options.ort?.wasm?.numThreads
2244
+ }
2245
+ });
2246
+ executor = {
2247
+ run(input, signal) {
2248
+ return bridge.run(
2249
+ { [input.inputName]: { data: input.data, dims: input.dims } },
2250
+ { signal }
2251
+ );
2252
+ },
2253
+ dispose: () => bridge.dispose()
2254
+ };
2255
+ activeBridge = void 0;
2256
+ } else {
2257
+ const session = await createOrtSession(modelBytes, candidatePlan, {
2258
+ ort: options.ort?.module,
2259
+ wasmPaths: options.ort?.wasm?.paths,
2260
+ numThreads: options.ort?.wasm?.numThreads
2261
+ });
2262
+ sessionMs = session.sessionMs;
2263
+ executor = {
2264
+ run(input, signal) {
2265
+ return session.run(
2266
+ { [input.inputName]: { data: input.data, dims: input.dims } },
2267
+ { signal }
2268
+ );
2269
+ },
2270
+ dispose: () => session.dispose()
2271
+ };
2272
+ }
2273
+ selectedPlan = candidatePlan;
2274
+ options.onProgress?.({ phase: "session", status: "complete" });
2275
+ break;
2276
+ } catch (error) {
2277
+ const mapped = error instanceof PPDetectionError ? error : new PPDetectionError("SESSION_CREATE_FAILED", String(error));
2278
+ const hasNext = options.allowFallback === true && candidate !== plan.candidates.at(-1);
2279
+ try {
2280
+ await executor?.dispose();
2281
+ } catch {
2282
+ }
2283
+ executor = void 0;
2284
+ try {
2285
+ await activeBridge?.dispose();
2286
+ } catch {
2287
+ }
2288
+ activeBridge = void 0;
2289
+ if (!hasNext) {
2290
+ throw mapped;
2291
+ }
2292
+ const fallback = {
2293
+ cause: mapped.cause ?? mapped,
2294
+ code: mapped.code,
2295
+ message: mapped.message,
2296
+ precision: candidate.precision,
2297
+ provider: candidate.backend,
2298
+ stage: "session",
2299
+ variantId: candidate.variantId
2300
+ };
2301
+ fallbacks.push(fallback);
2302
+ options.onProgress?.({ phase: "fallback", status: "complete", fallback });
2303
+ }
2304
+ }
2305
+ if (!executor) throw new PPDetectionError("SESSION_CREATE_FAILED", "\u65E0\u6CD5\u521B\u5EFA\u68C0\u6D4B Session");
2306
+ const loadedExecutor = executor;
2307
+ loadTimings = { ...loadTimings, sessionMs, totalMs: now4() - loadStartedAt };
2308
+ const detector = new PPDetectionDetectorImplementation({
2309
+ capabilities,
2310
+ manifest: runtimeManifest,
2311
+ model: modelInfo(runtimeManifest, variant3.id, actualSource),
2312
+ runtime: {
2313
+ requestedBackend: options.backend ?? "auto",
2314
+ backend: selectedPlan.actualBackend,
2315
+ precision: selectedPlan.actualPrecision,
2316
+ mode: selectedPlan.executionMode,
2317
+ fallbacks,
2318
+ capabilities
2319
+ },
2320
+ loadTimings,
2321
+ loadExecutor: () => Promise.resolve(loadedExecutor),
2322
+ onProgress: options.onProgress,
2323
+ clearCurrentModelCache: () => modelManager.clearCurrentModelCache(),
2324
+ clearAllCache: () => modelManager.clearAllCache(),
2325
+ getCacheEstimate: () => modelManager.getCacheEstimate(),
2326
+ disposeResources: () => modelManager.dispose()
2327
+ });
2328
+ await detector.load({ signal: options.signal });
2329
+ options.onProgress?.({ phase: "ready", status: "complete" });
2330
+ return detector;
2331
+ } catch (error) {
2332
+ try {
2333
+ await executor?.dispose();
2334
+ } catch {
2335
+ }
2336
+ try {
2337
+ await activeBridge?.dispose();
2338
+ } catch {
2339
+ }
2340
+ try {
2341
+ await modelManager.dispose();
2342
+ } catch {
2343
+ }
2344
+ throw error;
2345
+ }
2346
+ }
2347
+ async function clearModelCache() {
2348
+ const manager = new ModelManager();
2349
+ await manager.clearAllCache();
2350
+ await manager.dispose();
2351
+ }
2352
+
2353
+ export { CURRENT_SDK_VERSION, IndexedDBModelCache, MemoryModelCache, ModelManager, PPDetectionError, adaptModelManifest, clearModelCache, createOrtSession, createPPDetection, loadModelAsset, parseDetectionManifest, parseModelManifest, probeCapabilities, probePPDetectionCapabilities, resolveModelAsset, selectExecutionPlan };
2354
+ //# sourceMappingURL=index.js.map
2355
+ //# sourceMappingURL=index.js.map