web-sdk-pp-detection 0.3.0 → 0.3.2

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.d.ts CHANGED
@@ -138,9 +138,17 @@ interface PPDetectionProgressEvent {
138
138
  readonly status: "start" | "progress" | "complete";
139
139
  readonly loadedBytes?: number;
140
140
  readonly totalBytes?: number;
141
+ readonly attempt?: number;
142
+ readonly maxAttempts?: number;
141
143
  readonly fallback?: PPDetectionFallback;
142
144
  }
145
+ interface ModelDownloadOptions {
146
+ readonly timeoutMs?: number;
147
+ readonly idleTimeoutMs?: number;
148
+ readonly maxRetries?: number;
149
+ }
143
150
  interface CreatePPDetectionOptions {
151
+ readonly download?: ModelDownloadOptions;
144
152
  readonly allowExperimental?: boolean;
145
153
  readonly allowFallback?: boolean;
146
154
  readonly backend?: BackendPreference;
@@ -518,10 +526,13 @@ type ModelFetcher = (input: RequestInfo | URL, init?: RequestInit) => Promise<Re
518
526
  interface ModelDownloadProgress {
519
527
  readonly loadedBytes: number;
520
528
  readonly totalBytes?: number;
529
+ readonly attempt?: number;
530
+ readonly maxAttempts?: number;
521
531
  }
522
532
  interface LoadModelAssetOptions {
523
533
  readonly fetcher?: ModelFetcher;
524
534
  readonly signal?: AbortSignal;
535
+ readonly download?: ModelDownloadOptions;
525
536
  readonly onProgress?: (progress: ModelDownloadProgress) => void;
526
537
  }
527
538
  interface ModelBytes {
@@ -549,6 +560,7 @@ interface ModelCache {
549
560
  }
550
561
 
551
562
  interface ModelManagerOptions {
563
+ readonly download?: ModelDownloadOptions;
552
564
  readonly fetcher?: ModelFetcher;
553
565
  readonly cache?: "memory" | "indexeddb" | false | ModelCache;
554
566
  }
@@ -576,6 +588,7 @@ interface LoadedManagedModel {
576
588
  }
577
589
  declare class ModelManager {
578
590
  private readonly fetcher?;
591
+ private readonly download;
579
592
  private readonly cache;
580
593
  private readonly coordinator;
581
594
  private readonly lifecycle;
@@ -635,11 +648,11 @@ declare class IndexedDBModelCache implements ModelCache {
635
648
  declare global {
636
649
  var __PPDETECTION_SCRIPT_URL__: string | undefined;
637
650
  }
638
- declare const CURRENT_SDK_VERSION = "0.3.0";
651
+ declare const CURRENT_SDK_VERSION = "0.3.2";
639
652
 
640
653
  declare function probePPDetectionCapabilities(options?: CapabilityProbeOptions): DetectionCapabilities;
641
654
 
642
655
  declare function createPPDetection(options?: CreatePPDetectionOptions): Promise<PPDetectionDetectorImplementation>;
643
656
  declare function clearModelCache(): Promise<void>;
644
657
 
645
- export { type Backend, type BackendPreference, CURRENT_SDK_VERSION, type CacheEstimate, type CapabilityProbeOptions, type CreateOrtSessionOptions, type CreatePPDetectionOptions, type DecodedImage, type DetectOptions, type Detection, type DetectionBox, type DetectionCapabilities, type DetectionManifest, type DetectionModelVariant, type DetectionPoint, type DetectionPostprocessing, type DetectionPreprocessing, type DetectionTimings, type ExecutionCandidate, type ExecutionMode, type ExecutionPlan, type ImageRaster, type ImageSource, IndexedDBModelCache, type IndexedDBModelCacheOptions, type LoadManagedModelOptions, type LoadModelAssetOptions, type LoadedManagedModel, MemoryModelCache, type ModelBackend, type ModelBytes, type ModelCache, type ModelDownloadProgress, type ModelFetcher, type ModelInfo, ModelManager, type ModelManagerOptions, type ModelManifest, type ModelManifestMetadata, type ModelManifestSource, type ModelManifestVariant, type ModelPrecision, type ModelSource, type ModelSourceFailure, type ModelSourceKind, type ModelSourceSelection, type ModelVariant, type OrtModule, type OrtSessionHandle, type PPDetectionDetector, PPDetectionError, type PPDetectionErrorCode, type PPDetectionFallback, type PPDetectionLoadTimings, type PPDetectionModel, type PPDetectionModelInfo, type PPDetectionModelSourceInfo, type PPDetectionProgressEvent, type PPDetectionResult, type PPDetectionRuntimeInfo, type Precision, type ResolveModelAssetSelection, type ResolvedModelAsset, type RuntimeDetectionManifest, type RuntimeInfo, type SelectExecutionOptions, type TensorContract, type TimingBreakdown, type WorkerOrtOptions, type WorkerRequest, type WorkerResponse, adaptModelManifest, clearModelCache, createOrtSession, createPPDetection, loadModelAsset, parseDetectionManifest, parseModelManifest, probeCapabilities, probePPDetectionCapabilities, resolveModelAsset, selectExecutionPlan };
658
+ export { type Backend, type BackendPreference, CURRENT_SDK_VERSION, type CacheEstimate, type CapabilityProbeOptions, type CreateOrtSessionOptions, type CreatePPDetectionOptions, type DecodedImage, type DetectOptions, type Detection, type DetectionBox, type DetectionCapabilities, type DetectionManifest, type DetectionModelVariant, type DetectionPoint, type DetectionPostprocessing, type DetectionPreprocessing, type DetectionTimings, type ExecutionCandidate, type ExecutionMode, type ExecutionPlan, type ImageRaster, type ImageSource, IndexedDBModelCache, type IndexedDBModelCacheOptions, type LoadManagedModelOptions, type LoadModelAssetOptions, type LoadedManagedModel, MemoryModelCache, type ModelBackend, type ModelBytes, type ModelCache, type ModelDownloadOptions, type ModelDownloadProgress, type ModelFetcher, type ModelInfo, ModelManager, type ModelManagerOptions, type ModelManifest, type ModelManifestMetadata, type ModelManifestSource, type ModelManifestVariant, type ModelPrecision, type ModelSource, type ModelSourceFailure, type ModelSourceKind, type ModelSourceSelection, type ModelVariant, type OrtModule, type OrtSessionHandle, type PPDetectionDetector, PPDetectionError, type PPDetectionErrorCode, type PPDetectionFallback, type PPDetectionLoadTimings, type PPDetectionModel, type PPDetectionModelInfo, type PPDetectionModelSourceInfo, type PPDetectionProgressEvent, type PPDetectionResult, type PPDetectionRuntimeInfo, type Precision, type ResolveModelAssetSelection, type ResolvedModelAsset, type RuntimeDetectionManifest, type RuntimeInfo, type SelectExecutionOptions, type TensorContract, type TimingBreakdown, type WorkerOrtOptions, type WorkerRequest, type WorkerResponse, adaptModelManifest, clearModelCache, createOrtSession, createPPDetection, loadModelAsset, parseDetectionManifest, parseModelManifest, probeCapabilities, probePPDetectionCapabilities, resolveModelAsset, selectExecutionPlan };
package/dist/index.js CHANGED
@@ -371,34 +371,58 @@ function clipBicubic(value) {
371
371
  const rounded = value >> BICUBIC_PRECISION_BITS;
372
372
  return Math.max(0, Math.min(255, rounded));
373
373
  }
374
- function bicubicChannel(raster, resizedWidth, resizedHeight, channel) {
374
+ function writeBicubicChannels(raster, resizedWidth, resizedHeight, target) {
375
375
  const horizontal = createBicubicAxis(raster.width, resizedWidth);
376
376
  const vertical = createBicubicAxis(raster.height, resizedHeight);
377
- const intermediate = new Uint8Array(raster.height * resizedWidth);
377
+ const rowStride = resizedWidth * 3;
378
+ const intermediate = new Uint8Array(raster.height * rowStride);
378
379
  for (let y = 0; y < raster.height; y += 1) {
380
+ const sourceRow = y * raster.width * 4;
381
+ const targetRow = y * rowStride;
379
382
  for (let x = 0; x < resizedWidth; x += 1) {
380
383
  const [start, count] = horizontal.bounds[x];
381
384
  const coefficients = horizontal.coefficients[x];
382
- let sum = BICUBIC_ROUNDING;
385
+ let red = BICUBIC_ROUNDING;
386
+ let green = BICUBIC_ROUNDING;
387
+ let blue = BICUBIC_ROUNDING;
388
+ let source2 = sourceRow + start * 4;
383
389
  for (let index = 0; index < count; index += 1) {
384
- sum += raster.rgba[(y * raster.width + start + index) * 4 + channel] * coefficients[index];
390
+ const coefficient = coefficients[index];
391
+ red += raster.rgba[source2] * coefficient;
392
+ green += raster.rgba[source2 + 1] * coefficient;
393
+ blue += raster.rgba[source2 + 2] * coefficient;
394
+ source2 += 4;
385
395
  }
386
- intermediate[y * resizedWidth + x] = clipBicubic(sum);
396
+ const offset = targetRow + x * 3;
397
+ intermediate[offset] = clipBicubic(red);
398
+ intermediate[offset + 1] = clipBicubic(green);
399
+ intermediate[offset + 2] = clipBicubic(blue);
387
400
  }
388
401
  }
389
- const output = new Uint8Array(resizedWidth * resizedHeight);
402
+ const plane = target.inputWidth * target.inputHeight;
390
403
  for (let y = 0; y < resizedHeight; y += 1) {
391
404
  const [start, count] = vertical.bounds[y];
392
405
  const coefficients = vertical.coefficients[y];
406
+ const sourceRow = start * rowStride;
407
+ const targetRow = (y + target.padTop) * target.inputWidth + target.padLeft;
393
408
  for (let x = 0; x < resizedWidth; x += 1) {
394
- let sum = BICUBIC_ROUNDING;
409
+ let red = BICUBIC_ROUNDING;
410
+ let green = BICUBIC_ROUNDING;
411
+ let blue = BICUBIC_ROUNDING;
412
+ let source2 = sourceRow + x * 3;
395
413
  for (let index = 0; index < count; index += 1) {
396
- sum += intermediate[(start + index) * resizedWidth + x] * coefficients[index];
414
+ const coefficient = coefficients[index];
415
+ red += intermediate[source2] * coefficient;
416
+ green += intermediate[source2 + 1] * coefficient;
417
+ blue += intermediate[source2 + 2] * coefficient;
418
+ source2 += rowStride;
397
419
  }
398
- output[y * resizedWidth + x] = clipBicubic(sum);
420
+ const offset = targetRow + x;
421
+ target.data[offset] = target.values[clipBicubic(red)];
422
+ target.data[plane + offset] = target.values[256 + clipBicubic(green)];
423
+ target.data[plane * 2 + offset] = target.values[512 + clipBicubic(blue)];
399
424
  }
400
425
  }
401
- return output;
402
426
  }
403
427
  function preprocessImage(raster, preprocessing) {
404
428
  const inputWidth = preprocessing.size.width;
@@ -425,18 +449,35 @@ function preprocessImage(raster, preprocessing) {
425
449
  const mean = normalize ? preprocessing.mean ?? [0, 0, 0] : [0, 0, 0];
426
450
  const std = normalize ? preprocessing.std ?? [1, 1, 1] : [1, 1, 1];
427
451
  const interpolation2 = preprocessing.interpolation ?? "bilinear";
452
+ const bicubicValues = doResize && interpolation2 === "bicubic" ? new Float32Array(256 * 3) : void 0;
428
453
  for (let channel = 0; channel < 3; channel += 1) {
429
454
  const padding = normalize && mean[channel] !== 0 ? -mean[channel] / std[channel] : 0;
430
455
  data.fill(padding, channel * plane, (channel + 1) * plane);
431
- const bicubic = doResize && interpolation2 === "bicubic" ? bicubicChannel(raster, resizedWidth, resizedHeight, channel) : void 0;
456
+ if (bicubicValues) {
457
+ for (let pixel = 0; pixel < 256; pixel += 1) {
458
+ const scaled = rescale ? pixel * preprocessing.rescaleFactor : pixel;
459
+ bicubicValues[channel * 256 + pixel] = normalize ? (scaled - mean[channel]) / std[channel] : scaled;
460
+ }
461
+ continue;
462
+ }
432
463
  for (let y = 0; y < resizedHeight; y += 1) {
433
464
  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);
465
+ const pixel = doResize ? bilinearChannel(raster, x, y, resizedWidth, resizedHeight, channel) : sampleChannel(raster, x, y, channel);
435
466
  const scaled = rescale ? pixel * preprocessing.rescaleFactor : pixel;
436
467
  data[channel * plane + (y + padTop) * inputWidth + x + padLeft] = normalize ? (scaled - mean[channel]) / std[channel] : scaled;
437
468
  }
438
469
  }
439
470
  }
471
+ if (bicubicValues) {
472
+ writeBicubicChannels(raster, resizedWidth, resizedHeight, {
473
+ data,
474
+ inputWidth,
475
+ inputHeight,
476
+ padLeft,
477
+ padTop,
478
+ values: bicubicValues
479
+ });
480
+ }
440
481
  return {
441
482
  data,
442
483
  dims: [1, 3, inputHeight, inputWidth],
@@ -1433,11 +1474,36 @@ async function verifyModelIntegrity(bytes, expected, signal) {
1433
1474
  }
1434
1475
 
1435
1476
  // src/model/download.ts
1477
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
1478
+ var RetryableDownloadError = class extends PPDetectionError {
1479
+ constructor(message, details = {}, cause) {
1480
+ super("MODEL_DOWNLOAD_FAILED", message, details, { cause });
1481
+ }
1482
+ };
1483
+ function resolveDownloadOptions(options = {}) {
1484
+ if (options === null || typeof options !== "object")
1485
+ throw new PPDetectionError("INVALID_INPUT", "\u4E0B\u8F7D\u914D\u7F6E\u5FC5\u987B\u662F\u5BF9\u8C61");
1486
+ const resolved = {
1487
+ timeoutMs: options.timeoutMs === void 0 ? 18e4 : options.timeoutMs,
1488
+ idleTimeoutMs: options.idleTimeoutMs === void 0 ? 3e4 : options.idleTimeoutMs,
1489
+ maxRetries: options.maxRetries === void 0 ? 2 : options.maxRetries
1490
+ };
1491
+ for (const [key, value] of Object.entries(resolved)) {
1492
+ const maximum = key === "maxRetries" ? 5 : 2147483647;
1493
+ if (!Number.isSafeInteger(value) || value < 0 || value > maximum)
1494
+ throw new PPDetectionError("INVALID_INPUT", "\u4E0B\u8F7D\u53C2\u6570\u5FC5\u987B\u4E3A\u5141\u8BB8\u8303\u56F4\u5185\u7684\u5B89\u5168\u6574\u6570", {
1495
+ parameter: key,
1496
+ value,
1497
+ maximum
1498
+ });
1499
+ }
1500
+ return resolved;
1501
+ }
1436
1502
  function now() {
1437
1503
  return globalThis.performance?.now() ?? Date.now();
1438
1504
  }
1439
1505
  function aborted(error, signal) {
1440
- return signal?.aborted === true || error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
1506
+ return signal?.aborted === true || error instanceof Error && error.name === "AbortError";
1441
1507
  }
1442
1508
  function throwIfAborted3(signal) {
1443
1509
  if (signal?.aborted) throw new PPDetectionError("ABORTED", "\u6A21\u578B\u4E0B\u8F7D\u5DF2\u53D6\u6D88");
@@ -1465,106 +1531,206 @@ function validatePartialResponse(response, expectedBytes) {
1465
1531
  });
1466
1532
  }
1467
1533
  }
1468
- async function readResponse(response, expectedBytes, signal, onProgress) {
1469
- const totalBytes = contentLength(response, expectedBytes);
1470
- if (!response.body) {
1471
- const bytes2 = await response.arrayBuffer();
1472
- throwIfAborted3(signal);
1473
- onProgress?.({ loadedBytes: bytes2.byteLength, totalBytes });
1474
- return bytes2;
1534
+ function cancelQuietly(cancel) {
1535
+ try {
1536
+ void Promise.resolve(cancel()).catch(() => void 0);
1537
+ } catch {
1475
1538
  }
1476
- const reader = response.body.getReader();
1477
- const chunks = [];
1478
- let loadedBytes = 0;
1539
+ }
1540
+ async function downloadAttempt(asset, fetcher, options, policy, attempt) {
1541
+ const controller = new AbortController();
1542
+ let response;
1543
+ let reader;
1544
+ let active = true;
1545
+ let cancelledBody = false;
1546
+ let stopError;
1547
+ let totalTimer;
1548
+ let idleTimer;
1549
+ let rejectStop;
1550
+ const stopped = new Promise((_resolve, reject) => {
1551
+ rejectStop = reject;
1552
+ });
1553
+ void stopped.catch(() => void 0);
1554
+ const cancelBody = (reason) => {
1555
+ if (cancelledBody || !reader && !response?.body) return;
1556
+ cancelledBody = true;
1557
+ if (reader) cancelQuietly(() => reader.cancel(reason));
1558
+ else cancelQuietly(() => response.body.cancel(reason));
1559
+ };
1560
+ const stop = (error) => {
1561
+ if (!active || stopError) return;
1562
+ stopError = error;
1563
+ rejectStop(error);
1564
+ controller.abort();
1565
+ cancelBody(error);
1566
+ };
1567
+ const abort = () => stop(new PPDetectionError("ABORTED", "\u6A21\u578B\u4E0B\u8F7D\u5DF2\u53D6\u6D88"));
1568
+ const renewIdle = () => {
1569
+ clearTimeout(idleTimer);
1570
+ if (policy.idleTimeoutMs > 0)
1571
+ idleTimer = setTimeout(
1572
+ () => stop(
1573
+ new RetryableDownloadError("\u6A21\u578B\u4E0B\u8F7D\u957F\u65F6\u95F4\u6CA1\u6709\u65B0\u589E\u5B57\u8282", {
1574
+ idleTimeoutMs: policy.idleTimeoutMs
1575
+ })
1576
+ ),
1577
+ policy.idleTimeoutMs
1578
+ );
1579
+ };
1580
+ const checkActive = () => {
1581
+ if (stopError) throw stopError;
1582
+ throwIfAborted3(options.signal);
1583
+ };
1584
+ const network = async (operation) => {
1585
+ try {
1586
+ checkActive();
1587
+ const value = await Promise.race([operation(), stopped]);
1588
+ checkActive();
1589
+ return value;
1590
+ } catch (error) {
1591
+ if (stopError) throw stopError;
1592
+ if (aborted(error, options.signal))
1593
+ throw new PPDetectionError("ABORTED", "\u6A21\u578B\u4E0B\u8F7D\u5DF2\u53D6\u6D88", {}, { cause: error });
1594
+ if (error instanceof PPDetectionError) throw error;
1595
+ throw new RetryableDownloadError(
1596
+ "\u6A21\u578B\u4E0B\u8F7D\u8BF7\u6C42\u6216\u54CD\u5E94\u8BFB\u53D6\u5931\u8D25",
1597
+ { sourceKind: asset.source.kind, downloadUrl: asset.source.downloadUrl },
1598
+ error
1599
+ );
1600
+ }
1601
+ };
1602
+ const report = (loadedBytes) => {
1603
+ checkActive();
1604
+ options.onProgress?.({
1605
+ loadedBytes,
1606
+ totalBytes: asset.source.bytes,
1607
+ attempt,
1608
+ maxAttempts: policy.maxRetries + 1
1609
+ });
1610
+ checkActive();
1611
+ };
1612
+ options.signal?.addEventListener("abort", abort, { once: true });
1479
1613
  try {
1614
+ throwIfAborted3(options.signal);
1615
+ if (policy.timeoutMs > 0)
1616
+ totalTimer = setTimeout(
1617
+ () => stop(
1618
+ new RetryableDownloadError("\u6A21\u578B\u4E0B\u8F7D\u8D85\u8FC7\u8BF7\u6C42\u603B\u65F6\u9650", { timeoutMs: policy.timeoutMs })
1619
+ ),
1620
+ policy.timeoutMs
1621
+ );
1622
+ renewIdle();
1623
+ report(0);
1624
+ response = await network(
1625
+ () => Promise.resolve(
1626
+ fetcher(asset.source.downloadUrl, {
1627
+ signal: controller.signal,
1628
+ ...attempt > 1 ? { cache: "reload" } : {}
1629
+ })
1630
+ ).then((value) => {
1631
+ response = value;
1632
+ if (!active || stopError) cancelBody(stopError);
1633
+ return value;
1634
+ })
1635
+ );
1636
+ if (!response.ok) {
1637
+ const details = {
1638
+ sourceKind: asset.source.kind,
1639
+ status: response.status,
1640
+ statusText: response.statusText
1641
+ };
1642
+ if (RETRYABLE_STATUSES.has(response.status))
1643
+ throw new RetryableDownloadError("\u6A21\u578B\u4E0B\u8F7D\u8FD4\u56DE\u53EF\u91CD\u8BD5\u72B6\u6001", details);
1644
+ throw new PPDetectionError("MODEL_DOWNLOAD_FAILED", "\u6A21\u578B\u4E0B\u8F7D\u8FD4\u56DE\u975E\u6210\u529F\u72B6\u6001", details);
1645
+ }
1646
+ validatePartialResponse(response, asset.source.bytes);
1647
+ contentLength(response, asset.source.bytes);
1648
+ if (!response.body) {
1649
+ const bytes2 = await network(() => response.arrayBuffer());
1650
+ report(bytes2.byteLength);
1651
+ return bytes2;
1652
+ }
1653
+ reader = response.body.getReader();
1654
+ const chunks = [];
1655
+ let loadedBytes = 0;
1480
1656
  while (true) {
1481
- throwIfAborted3(signal);
1482
- const chunk = await reader.read();
1657
+ const chunk = await network(() => reader.read());
1483
1658
  if (chunk.done) break;
1659
+ if (chunk.value.byteLength === 0) continue;
1484
1660
  loadedBytes += chunk.value.byteLength;
1485
- if (loadedBytes > expectedBytes) {
1661
+ if (loadedBytes > asset.source.bytes)
1486
1662
  throw new PPDetectionError("MODEL_INTEGRITY_FAILED", "\u6A21\u578B\u54CD\u5E94\u8D85\u8FC7\u6E05\u5355\u58F0\u660E\u5927\u5C0F", {
1487
- expectedBytes,
1663
+ expectedBytes: asset.source.bytes,
1488
1664
  loadedBytes
1489
1665
  });
1490
- }
1491
1666
  chunks.push(chunk.value);
1492
- onProgress?.({ loadedBytes, totalBytes });
1667
+ renewIdle();
1668
+ report(loadedBytes);
1493
1669
  }
1670
+ const bytes = new Uint8Array(loadedBytes);
1671
+ let offset = 0;
1672
+ for (const chunk of chunks) {
1673
+ bytes.set(chunk, offset);
1674
+ offset += chunk.byteLength;
1675
+ }
1676
+ report(loadedBytes);
1677
+ return bytes.buffer;
1494
1678
  } catch (error) {
1679
+ controller.abort();
1680
+ cancelBody(error);
1681
+ throw error;
1682
+ } finally {
1683
+ active = false;
1684
+ clearTimeout(totalTimer);
1685
+ clearTimeout(idleTimer);
1686
+ options.signal?.removeEventListener("abort", abort);
1495
1687
  try {
1496
- await reader.cancel(error);
1688
+ reader?.releaseLock();
1497
1689
  } catch {
1498
1690
  }
1499
- throw error;
1500
- } finally {
1501
- reader.releaseLock();
1502
1691
  }
1503
- const bytes = new Uint8Array(loadedBytes);
1504
- let offset = 0;
1505
- for (const chunk of chunks) {
1506
- bytes.set(chunk, offset);
1507
- offset += chunk.byteLength;
1508
- }
1509
- onProgress?.({ loadedBytes, totalBytes });
1510
- return bytes.buffer;
1692
+ }
1693
+ function waitForRetry(delayMs, signal) {
1694
+ throwIfAborted3(signal);
1695
+ return new Promise((resolve, reject) => {
1696
+ const abort = () => {
1697
+ clearTimeout(timer);
1698
+ signal?.removeEventListener("abort", abort);
1699
+ reject(new PPDetectionError("ABORTED", "\u6A21\u578B\u4E0B\u8F7D\u5DF2\u53D6\u6D88"));
1700
+ };
1701
+ const timer = setTimeout(() => {
1702
+ signal?.removeEventListener("abort", abort);
1703
+ resolve();
1704
+ }, delayMs);
1705
+ signal?.addEventListener("abort", abort, { once: true });
1706
+ if (signal?.aborted) abort();
1707
+ });
1511
1708
  }
1512
1709
  async function loadModelAsset(asset, options = {}) {
1513
1710
  throwIfAborted3(options.signal);
1711
+ const policy = resolveDownloadOptions(options.download);
1514
1712
  const fetcher = options.fetcher ?? globalThis.fetch?.bind(globalThis);
1515
1713
  if (!fetcher)
1516
1714
  throw new PPDetectionError("MODEL_DOWNLOAD_FAILED", "\u5F53\u524D\u73AF\u5883\u6CA1\u6709 fetch API", {
1517
1715
  sourceKind: asset.source.kind
1518
1716
  });
1519
1717
  const downloadStarted = now();
1520
- let response;
1521
- try {
1522
- response = await fetcher(asset.source.downloadUrl, { signal: options.signal });
1523
- } catch (error) {
1524
- if (aborted(error, options.signal))
1525
- throw new PPDetectionError("ABORTED", "\u6A21\u578B\u4E0B\u8F7D\u5DF2\u53D6\u6D88", {}, { cause: error });
1526
- throw new PPDetectionError(
1527
- "MODEL_DOWNLOAD_FAILED",
1528
- "\u6A21\u578B\u4E0B\u8F7D\u8BF7\u6C42\u5931\u8D25",
1529
- {
1530
- sourceKind: asset.source.kind,
1531
- downloadUrl: asset.source.downloadUrl
1532
- },
1533
- { cause: error }
1534
- );
1535
- }
1536
- if (!response.ok) {
1537
- throw new PPDetectionError("MODEL_DOWNLOAD_FAILED", "\u6A21\u578B\u4E0B\u8F7D\u8FD4\u56DE\u975E\u6210\u529F\u72B6\u6001", {
1538
- sourceKind: asset.source.kind,
1539
- status: response.status,
1540
- statusText: response.statusText
1541
- });
1542
- }
1543
- validatePartialResponse(response, asset.source.bytes);
1544
1718
  let bytes;
1545
- try {
1546
- bytes = await readResponse(response, asset.source.bytes, options.signal, options.onProgress);
1547
- } catch (error) {
1548
- if (error instanceof PPDetectionError) throw error;
1549
- if (aborted(error, options.signal))
1550
- throw new PPDetectionError("ABORTED", "\u6A21\u578B\u4E0B\u8F7D\u5DF2\u53D6\u6D88", {}, { cause: error });
1551
- throw new PPDetectionError(
1552
- "MODEL_DOWNLOAD_FAILED",
1553
- "\u8BFB\u53D6\u6A21\u578B\u54CD\u5E94\u5931\u8D25",
1554
- { sourceKind: asset.source.kind },
1555
- { cause: error }
1556
- );
1719
+ for (let attempt = 1; ; attempt++) {
1720
+ throwIfAborted3(options.signal);
1721
+ try {
1722
+ bytes = await downloadAttempt(asset, fetcher, options, policy, attempt);
1723
+ break;
1724
+ } catch (error) {
1725
+ throwIfAborted3(options.signal);
1726
+ if (!(error instanceof RetryableDownloadError) || attempt > policy.maxRetries) throw error;
1727
+ await waitForRetry(Math.min(500 * 2 ** (attempt - 1), 4e3), options.signal);
1728
+ }
1557
1729
  }
1558
1730
  const modelDownloadMs = now() - downloadStarted;
1559
1731
  const integrityStarted = now();
1560
1732
  await verifyModelIntegrity(bytes, asset.source, options.signal);
1561
- return {
1562
- bytes,
1563
- timings: {
1564
- modelDownloadMs,
1565
- integrityMs: now() - integrityStarted
1566
- }
1567
- };
1733
+ return { bytes, timings: { modelDownloadMs, integrityMs: now() - integrityStarted } };
1568
1734
  }
1569
1735
 
1570
1736
  // src/model/source-resolver.ts
@@ -1631,6 +1797,7 @@ function throwIfAborted4(signal) {
1631
1797
  }
1632
1798
  var ModelManager = class {
1633
1799
  fetcher;
1800
+ download;
1634
1801
  cache;
1635
1802
  coordinator;
1636
1803
  lifecycle = new AbortController();
@@ -1639,6 +1806,7 @@ var ModelManager = class {
1639
1806
  disposed = false;
1640
1807
  disposePromise;
1641
1808
  constructor(options = {}) {
1809
+ this.download = resolveDownloadOptions(options.download);
1642
1810
  this.fetcher = options.fetcher;
1643
1811
  this.cache = createCache(options.cache);
1644
1812
  this.coordinator = coordinateCache(this.cache);
@@ -1735,6 +1903,7 @@ var ModelManager = class {
1735
1903
  try {
1736
1904
  loaded = await loadModelAsset(asset, {
1737
1905
  fetcher: this.fetcher,
1906
+ download: this.download,
1738
1907
  signal: options.signal,
1739
1908
  onProgress: options.onProgress
1740
1909
  });
@@ -2219,7 +2388,7 @@ var WorkerBridge = class {
2219
2388
  };
2220
2389
 
2221
2390
  // src/index.ts
2222
- var CURRENT_SDK_VERSION = "0.3.0";
2391
+ var CURRENT_SDK_VERSION = "0.3.2";
2223
2392
  function probePPDetectionCapabilities(options = {}) {
2224
2393
  return probeCapabilities(options);
2225
2394
  }
@@ -2344,6 +2513,7 @@ async function createPPDetection(options = {}) {
2344
2513
  simpleManifest(runtimeManifest)
2345
2514
  );
2346
2515
  const modelManager = new ModelManager({
2516
+ download: options.download,
2347
2517
  cache: options.cache === false ? false : options.cache === "memory" ? "memory" : void 0
2348
2518
  });
2349
2519
  let executor;