web-sdk-pp-detection 0.1.1 → 0.2.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/README.md CHANGED
@@ -4,13 +4,15 @@
4
4
 
5
5
  基于 ONNX Runtime Web 的浏览器端 PP-Detection 目标检测 SDK,支持 PC、移动端与各类 H5 页面。
6
6
 
7
+ 当前 SDK 版本为 **0.2.0**,完整变更见[发布说明](https://github.com/chenmohan123/web-sdk-PP-Detection/blob/main/docs/zh-CN/release-0.2.0.md)。
8
+
7
9
  ## 安装
8
10
 
9
11
  ```bash
10
- pnpm add web-sdk-pp-detection
12
+ pnpm add web-sdk-pp-detection@0.2.0
11
13
  ```
12
14
 
13
- 也可以使用 `npm install web-sdk-pp-detection`。
15
+ 也可以使用 `npm install web-sdk-pp-detection@0.2.0`。
14
16
 
15
17
  ## 快速开始
16
18
 
@@ -35,9 +37,11 @@ await detector.dispose();
35
37
 
36
38
  `classThresholds` 按 manifest 标签名称覆盖目标检测置信度过滤阈值,未配置的类别回退到全局 `threshold`。未知类别名称或超出 `0` 到 `1` 的值会被拒绝。
37
39
 
38
- 当前发布版本内置 PicoDet 1.0.1 FP32 stable manifest;使用默认模型时可以省略 `model`。自定义模型仍应传入经过验证的 runtime manifest 或清单对象。
40
+ 工厂必须显式传入 `model` 或 `manifest`,两者均缺省时抛出 `INVALID_MANIFEST`;同时传入时优先使用 `model`。仓库提供 PicoDet 1.0.1 FP32 stable manifest,npm 包不包含 ONNX 模型本体。自定义模型也应传入经过验证的 runtime manifest 或清单对象。
41
+
42
+ 仓库模型 manifest 默认来源为 Hugging Face;在线 Demo 的来源选择默认设为 ModelScope,两者分别属于模型清单和 Demo 配置。
39
43
 
40
- 模型初始化耗时可以通过 `detector.loadTimings` 查看。`totalMs` 是初始化总耗时,同时提供 `modelDownloadMs`(网络下载)、`modelCacheReadMs`(缓存读取)、`integrityMs`(SHA-256 完整性校验)和 `sessionMs`(ONNX Runtime Session 创建)。发布版本内置 PicoDet 1.0.1 FP32 stable manifest;使用自定义模型时请传入已验证的 runtime manifest 或清单对象。
44
+ 模型初始化耗时可以通过 `detector.loadTimings` 查看。`totalMs` 是初始化总耗时,同时提供 `modelDownloadMs`(网络下载)、`modelCacheReadMs`(缓存读取)、`integrityMs`(SHA-256 完整性校验)和 `sessionMs`(ONNX Runtime Session 创建)。从 0.2.0 起,`loadTimings.modelSource` 区分 `network`、`cache`、`memory`,`runtime.runtimeVersion/environment` 记录实际 ORT 版本和环境快照。初始化总耗时包含 manifest 获取,详细语义见[性能文档](https://github.com/chenmohan123/web-sdk-PP-Detection/blob/main/docs/zh-CN/performance.md)。
41
45
 
42
46
  ## 运行后端与精度
43
47
 
@@ -61,6 +65,8 @@ const detector = await createPPDetection({
61
65
 
62
66
  模型加载进度通过 `onProgress` 回调提供。`phase: "model"`、`status: "progress"` 事件中的 `loadedBytes` 和可选的 `totalBytes` 仅表示网络下载字节,不代表完整初始化百分比,也不包含完整性校验或 Session 创建。响应没有 `Content-Length` 时 `totalBytes` 可能缺失,缓存、内存或自定义二进制模型也可能不产生字节进度。可捕获结构化的 `PPDetectionError` 并读取 `code` 与 `details`。SDK 支持模型缓存;可以通过 detector 的缓存方法查询或清理。使用结束后必须调用 `dispose()` 释放 Worker、ONNX Runtime session 与 GPU/CPU 资源。
63
67
 
68
+ 从 0.2.0 起,无活动检测器时可使用 `ModelManager.getCacheEstimate({ id, version })`、`clearCurrentModelCache({ id, version })` 统计或清理同一模型的全部变体/来源。可选的 `ModelCache.scope/list()` 支持自定义缓存协调和模型枚举;缺少 `list()` 的自定义缓存拒绝按模型操作,不扩大清理范围。同一 JavaScript 执行环境内,清理前启动的迟到下载不会重新填充缓存;跨标签页/Worker 的并发协调不在保证范围内。完整契约见 [API](https://github.com/chenmohan123/web-sdk-PP-Detection/blob/main/docs/zh-CN/api.md)。
69
+
64
70
  ## 微信环境
65
71
 
66
72
  支持微信公众号页面以及微信内嵌浏览器中的 H5/WebView 集成。当前不宣称支持 native Mini Program 原生小程序直接推理;原生小程序需要通过 WebView 承载 H5 页面或使用服务端推理。
@@ -76,13 +82,15 @@ const detector = await createPPDetection({
76
82
 
77
83
  A browser-first PP-Detection object detection SDK powered by ONNX Runtime Web for desktop, mobile, and H5 pages.
78
84
 
85
+ The current SDK version is **0.2.0**. See the [release notes](https://github.com/chenmohan123/web-sdk-PP-Detection/blob/main/docs/en/release-0.2.0.md) for the complete changes.
86
+
79
87
  ### Installation
80
88
 
81
89
  ```bash
82
- pnpm add web-sdk-pp-detection
90
+ pnpm add web-sdk-pp-detection@0.2.0
83
91
  ```
84
92
 
85
- `npm install web-sdk-pp-detection` is also supported.
93
+ `npm install web-sdk-pp-detection@0.2.0` is also supported.
86
94
 
87
95
  ### Quick start
88
96
 
@@ -107,9 +115,11 @@ await detector.dispose();
107
115
 
108
116
  `classThresholds` overrides object-detection confidence filtering for matching manifest label names and falls back to the global `threshold` for unspecified classes. Unknown class names and values outside `0` through `1` are rejected.
109
117
 
110
- The release includes a built-in PicoDet 1.0.1 FP32 stable manifest, so `model` may be omitted for the default model. Pass a verified runtime or custom manifest when using another model.
118
+ The factory requires an explicit `model` or `manifest`; omitting both throws `INVALID_MANIFEST`, and `model` takes precedence when both are supplied. The repository provides the PicoDet 1.0.1 FP32 stable manifest; the npm package contains no ONNX model binary. Custom models also require a verified runtime or custom manifest.
119
+
120
+ The repository model manifest defaults to Hugging Face. The live Demo independently configures its source selector to default to ModelScope.
111
121
 
112
- Detailed initialization timings are available through `detector.loadTimings`. `totalMs` is the full initialization duration. The additive fields `modelDownloadMs`, `modelCacheReadMs`, `integrityMs`, and `sessionMs` separate network download, cache reads, SHA-256 verification, and Session creation. The release includes the PicoDet 1.0.1 FP32 stable manifest; pass a verified runtime or custom model manifest when using another model.
122
+ Detailed initialization timings are available through `detector.loadTimings`. `totalMs` is the full initialization duration. The additive fields `modelDownloadMs`, `modelCacheReadMs`, `integrityMs`, and `sessionMs` separate network download, cache reads, SHA-256 verification, and Session creation. From 0.2.0, `loadTimings.modelSource` distinguishes `network`, `cache`, and `memory`, while `runtime.runtimeVersion/environment` capture the actual ORT version and environment. Initialization totals include manifest retrieval; see [performance](https://github.com/chenmohan123/web-sdk-PP-Detection/blob/main/docs/en/performance.md) for the full semantics.
113
123
 
114
124
  ### Backend and precision
115
125
 
@@ -133,6 +143,8 @@ const detector = await createPPDetection({
133
143
 
134
144
  Use `onProgress` for model loading progress. On `phase: "model"`, `status: "progress"` events, `loadedBytes` and the optional `totalBytes` describe network-transfer bytes only; they are not an overall initialization percentage and exclude integrity verification and Session creation. `totalBytes` can be absent without a `Content-Length` response header, while cache, memory, or custom binary model sources may emit no byte progress. Structured failures are exposed as `PPDetectionError` with `code` and `details`. Model cache entries can be listed or cleared through the detector. Always call `dispose()` when finished to release the Worker, ONNX Runtime session, and GPU/CPU resources.
135
145
 
146
+ From 0.2.0, without an active detector, `ModelManager.getCacheEstimate({ id, version })` and `clearCurrentModelCache({ id, version })` inspect or clear all variants/sources of the model. Optional `ModelCache.scope/list()` support custom cache coordination and model enumeration; a custom cache without `list()` rejects identity-based operations without broader deletion. Within the same JavaScript execution environment, downloads started before clearing cannot repopulate caches; coordination across tabs/Workers is outside this guarantee. See the [API](https://github.com/chenmohan123/web-sdk-PP-Detection/blob/main/docs/en/api.md).
147
+
136
148
  ### WeChat environments
137
149
 
138
150
  WeChat official-account pages and other H5/WebView integrations are supported. Native Mini Program inference is not claimed; a native Mini Program should host the H5 experience in a WebView or use server-side inference.
@@ -14258,7 +14258,11 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
14258
14258
  original: { width: decoded.width, height: decoded.height }
14259
14259
  },
14260
14260
  model: this.model,
14261
- runtime: this.runtime,
14261
+ runtime: {
14262
+ ...this.runtime,
14263
+ fallbacks: this.runtime.fallbacks.map((fallback) => ({ ...fallback })),
14264
+ ...this.runtime.environment ? { environment: { ...this.runtime.environment } } : {}
14265
+ },
14262
14266
  timings: {
14263
14267
  decodeMs: decoded.decodeMs,
14264
14268
  preprocessMs,
@@ -14751,8 +14755,84 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
14751
14755
  });
14752
14756
  }
14753
14757
 
14758
+ // src/cache/coordination.ts
14759
+ var CacheCoordinator = class {
14760
+ caches = /* @__PURE__ */ new Map();
14761
+ generation = 0;
14762
+ keys = /* @__PURE__ */ new Map();
14763
+ pending = Promise.resolve();
14764
+ guard(key) {
14765
+ const generation = this.generation;
14766
+ const keyGeneration = this.keys.get(key) ?? 0;
14767
+ this.keys.set(key, keyGeneration);
14768
+ return () => generation === this.generation && keyGeneration === (this.keys.get(key) ?? 0);
14769
+ }
14770
+ run(operation) {
14771
+ const pending = this.pending.then(operation);
14772
+ this.pending = pending.catch(() => void 0);
14773
+ return pending;
14774
+ }
14775
+ clear(key, matches) {
14776
+ if (matches) {
14777
+ for (const [candidate, generation] of this.keys) {
14778
+ if (matches(candidate)) this.keys.set(candidate, generation + 1);
14779
+ }
14780
+ } else if (key === void 0) this.generation += 1;
14781
+ else this.keys.set(key, (this.keys.get(key) ?? 0) + 1);
14782
+ return this.run(async () => {
14783
+ const outcomes = await Promise.allSettled(
14784
+ [...this.caches.keys()].map(async (cache) => {
14785
+ if (matches && cache.list) {
14786
+ for (const entry of await cache.list()) {
14787
+ if (matches(entry.key)) await cache.clearCurrent(entry.key);
14788
+ }
14789
+ } else if (key === void 0) await cache.clearAll();
14790
+ else await cache.clearCurrent(key);
14791
+ })
14792
+ );
14793
+ const failure = outcomes.find((outcome) => outcome.status === "rejected");
14794
+ if (failure?.status === "rejected") throw failure.reason;
14795
+ });
14796
+ }
14797
+ unregister(cache) {
14798
+ const count = this.caches.get(cache) ?? 0;
14799
+ if (count > 1) {
14800
+ this.caches.set(cache, count - 1);
14801
+ return false;
14802
+ }
14803
+ this.caches.delete(cache);
14804
+ return true;
14805
+ }
14806
+ };
14807
+ var coordinators = /* @__PURE__ */ new WeakMap();
14808
+ function coordinateCache(cache) {
14809
+ const scope = cache.scope ?? cache;
14810
+ let coordinator = coordinators.get(scope);
14811
+ if (!coordinator) {
14812
+ coordinator = new CacheCoordinator();
14813
+ coordinators.set(scope, coordinator);
14814
+ }
14815
+ coordinator.caches.set(cache, (coordinator.caches.get(cache) ?? 0) + 1);
14816
+ return coordinator;
14817
+ }
14818
+ var databaseScopes = /* @__PURE__ */ new WeakMap();
14819
+ function databaseScope(factory, name) {
14820
+ let scopes = databaseScopes.get(factory);
14821
+ if (!scopes) {
14822
+ scopes = /* @__PURE__ */ new Map();
14823
+ databaseScopes.set(factory, scopes);
14824
+ }
14825
+ let scope = scopes.get(name);
14826
+ if (!scope) {
14827
+ scope = {};
14828
+ scopes.set(name, scope);
14829
+ }
14830
+ return scope;
14831
+ }
14832
+
14754
14833
  // src/cache/indexeddb-cache.ts
14755
14834
  var IndexedDBModelCache = class {
14835
+ scope;
14756
14836
  factory;
14757
14837
  databaseName;
14758
14838
  database;
@@ -14761,6 +14841,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
14761
14841
  if (!factory) throw new Error("\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301 IndexedDB");
14762
14842
  this.factory = factory;
14763
14843
  this.databaseName = options.databaseName ?? "web-sdk-pp-detection-models-v1";
14844
+ this.scope = databaseScope(factory, this.databaseName);
14764
14845
  }
14765
14846
  async get(key) {
14766
14847
  const record3 = await this.request(
@@ -14814,6 +14895,24 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
14814
14895
  (await this.database).close();
14815
14896
  this.database = void 0;
14816
14897
  }
14898
+ async list() {
14899
+ const database = await this.open();
14900
+ return await new Promise((resolve, reject) => {
14901
+ const transaction = database.transaction("models", "readonly");
14902
+ const request = transaction.objectStore("models").openCursor();
14903
+ const entries = [];
14904
+ request.onerror = () => reject(request.error ?? new Error("\u8BFB\u53D6\u7F13\u5B58\u5BB9\u91CF\u5931\u8D25"));
14905
+ request.onsuccess = () => {
14906
+ const cursor = request.result;
14907
+ if (!cursor) return;
14908
+ const record3 = cursor.value;
14909
+ entries.push({ key: record3.key, bytes: record3.size });
14910
+ cursor.continue();
14911
+ };
14912
+ transaction.oncomplete = () => resolve(entries);
14913
+ transaction.onabort = () => reject(transaction.error ?? new Error("\u7F13\u5B58\u5BB9\u91CF\u4E8B\u52A1\u4E2D\u6B62"));
14914
+ });
14915
+ }
14817
14916
  open() {
14818
14917
  if (this.database) return this.database;
14819
14918
  this.database = new Promise((resolve, reject) => {
@@ -14883,6 +14982,11 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
14883
14982
  this.entries.clear();
14884
14983
  return Promise.resolve();
14885
14984
  }
14985
+ list() {
14986
+ return Promise.resolve(
14987
+ [...this.entries].map(([key, bytes]) => ({ key, bytes: bytes.byteLength }))
14988
+ );
14989
+ }
14886
14990
  };
14887
14991
 
14888
14992
  // src/cache/model-cache.ts
@@ -14890,9 +14994,11 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
14890
14994
  constructor(memory, persistent) {
14891
14995
  this.memory = memory;
14892
14996
  this.persistent = persistent;
14997
+ this.scope = persistent?.scope ?? persistent ?? memory;
14893
14998
  }
14894
14999
  memory;
14895
15000
  persistent;
15001
+ scope;
14896
15002
  async get(key) {
14897
15003
  const memoryValue = await this.memory.get(key);
14898
15004
  if (memoryValue) return memoryValue;
@@ -14915,6 +15021,13 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
14915
15021
  estimate() {
14916
15022
  return this.persistent?.estimate() ?? this.memory.estimate();
14917
15023
  }
15024
+ async list() {
15025
+ const entries = /* @__PURE__ */ new Map();
15026
+ for (const cache of [this.memory, this.persistent]) {
15027
+ for (const entry of await cache?.list?.() ?? []) entries.set(entry.key, entry);
15028
+ }
15029
+ return [...entries.values()];
15030
+ }
14918
15031
  async close() {
14919
15032
  await this.memory.close?.();
14920
15033
  await this.persistent?.close?.();
@@ -15124,6 +15237,14 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15124
15237
 
15125
15238
  // src/model/model-manager.ts
15126
15239
  var SDK_CACHE_NAMESPACE = "web-sdk-pp-detection:cache-v1";
15240
+ function matchesModel(key, model) {
15241
+ try {
15242
+ const parts = JSON.parse(key);
15243
+ return Array.isArray(parts) && parts[0] === SDK_CACHE_NAMESPACE && (model === void 0 || parts[1] === model.id && parts[2] === model.version);
15244
+ } catch {
15245
+ return false;
15246
+ }
15247
+ }
15127
15248
  function now2() {
15128
15249
  return globalThis.performance?.now() ?? Date.now();
15129
15250
  }
@@ -15147,6 +15268,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15147
15268
  var ModelManager = class {
15148
15269
  fetcher;
15149
15270
  cache;
15271
+ coordinator;
15150
15272
  lifecycle = new AbortController();
15151
15273
  activeLoads = /* @__PURE__ */ new Set();
15152
15274
  currentKey;
@@ -15155,6 +15277,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15155
15277
  constructor(options = {}) {
15156
15278
  this.fetcher = options.fetcher;
15157
15279
  this.cache = createCache(options.cache);
15280
+ this.coordinator = coordinateCache(this.cache);
15158
15281
  }
15159
15282
  cacheKey(variant3, source2, model = { id: "unknown", version: "unknown" }) {
15160
15283
  return JSON.stringify([
@@ -15193,16 +15316,24 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15193
15316
  const variant3 = resolveModelVariant(manifest, options.variantId);
15194
15317
  const sourceKind2 = options.sourceKind ?? "auto";
15195
15318
  const sources = resolveModelSources(variant3, sourceKind2);
15319
+ const guards = new Map(
15320
+ sources.map((source2) => {
15321
+ const key = this.cacheKey(variant3, source2, manifest.model);
15322
+ return [key, this.coordinator.guard(key)];
15323
+ })
15324
+ );
15196
15325
  const failures = [];
15197
15326
  let lastError;
15198
15327
  for (const source2 of sources) {
15199
15328
  throwIfAborted4(options.signal);
15200
15329
  const asset = { model: manifest.model, variant: variant3, source: source2 };
15201
15330
  const cacheKey = this.cacheKey(variant3, source2, manifest.model);
15331
+ this.currentKey = cacheKey;
15332
+ const canMutate = guards.get(cacheKey);
15202
15333
  const cacheStarted = now2();
15203
15334
  let cached;
15204
15335
  try {
15205
- cached = await this.cache.get(cacheKey);
15336
+ cached = await this.coordinator.run(() => this.cache.get(cacheKey));
15206
15337
  } catch (error) {
15207
15338
  if (error instanceof PPDetectionError && error.code === "ABORTED") throw error;
15208
15339
  throwIfAborted4(options.signal);
@@ -15213,7 +15344,6 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15213
15344
  const integrityStarted = now2();
15214
15345
  try {
15215
15346
  await verifyModelIntegrity(cached, source2, options.signal);
15216
- this.currentKey = cacheKey;
15217
15347
  return {
15218
15348
  bytes: cached,
15219
15349
  manifest,
@@ -15227,7 +15357,9 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15227
15357
  } catch (error) {
15228
15358
  if (error instanceof PPDetectionError && error.code === "ABORTED") throw error;
15229
15359
  try {
15230
- await this.cache.clearCurrent(cacheKey);
15360
+ await this.coordinator.run(async () => {
15361
+ if (canMutate()) await this.cache.clearCurrent(cacheKey);
15362
+ });
15231
15363
  } catch (clearError) {
15232
15364
  if (clearError instanceof PPDetectionError && clearError.code === "ABORTED")
15233
15365
  throw clearError;
@@ -15263,11 +15395,12 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15263
15395
  }
15264
15396
  throwIfAborted4(options.signal);
15265
15397
  try {
15266
- await this.cache.put(cacheKey, loaded.bytes);
15398
+ await this.coordinator.run(async () => {
15399
+ if (canMutate()) await this.cache.put(cacheKey, loaded.bytes);
15400
+ });
15267
15401
  } catch {
15268
15402
  }
15269
15403
  throwIfAborted4(options.signal);
15270
- this.currentKey = cacheKey;
15271
15404
  return {
15272
15405
  bytes: loaded.bytes,
15273
15406
  manifest,
@@ -15292,16 +15425,44 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15292
15425
  estimate() {
15293
15426
  return this.getCacheEstimate();
15294
15427
  }
15295
- getCacheEstimate() {
15296
- return this.cache.estimate();
15428
+ getCacheEstimate(model) {
15429
+ return this.coordinator.run(async () => {
15430
+ if (!this.cache.list) {
15431
+ if (model)
15432
+ throw new PPDetectionError(
15433
+ "CAPABILITY_UNSUPPORTED",
15434
+ "\u81EA\u5B9A\u4E49\u7F13\u5B58\u9700\u5B9E\u73B0 list \u624D\u80FD\u6309\u6A21\u578B\u4F30\u7B97"
15435
+ );
15436
+ return this.cache.estimate();
15437
+ }
15438
+ const unique = /* @__PURE__ */ new Map();
15439
+ for (const cache of this.coordinator.caches.keys()) {
15440
+ for (const entry of await cache.list?.() ?? []) unique.set(entry.key, entry);
15441
+ }
15442
+ const entries = [...unique.values()].filter((entry) => matchesModel(entry.key, model));
15443
+ return {
15444
+ bytes: entries.reduce((sum, entry) => sum + entry.bytes, 0),
15445
+ entries: entries.length
15446
+ };
15447
+ });
15297
15448
  }
15298
- async clearCurrentModelCache() {
15449
+ async clearCurrentModelCache(model) {
15450
+ if (model) {
15451
+ if (!model.id || !model.version)
15452
+ throw new PPDetectionError("INVALID_MANIFEST", "\u7F13\u5B58\u6A21\u578B\u8EAB\u4EFD\u5FC5\u987B\u5305\u542B id \u548C version");
15453
+ if (!this.cache.list)
15454
+ throw new PPDetectionError(
15455
+ "CAPABILITY_UNSUPPORTED",
15456
+ "\u81EA\u5B9A\u4E49\u7F13\u5B58\u9700\u5B9E\u73B0 list \u624D\u80FD\u6309\u6A21\u578B\u6E05\u7406"
15457
+ );
15458
+ await this.coordinator.clear(void 0, (key) => matchesModel(key, model));
15459
+ return;
15460
+ }
15299
15461
  if (!this.currentKey) return;
15300
- await this.cache.clearCurrent(this.currentKey);
15462
+ await this.coordinator.clear(this.currentKey);
15301
15463
  }
15302
15464
  async clearAllCache() {
15303
- await this.cache.clearAll();
15304
- this.currentKey = void 0;
15465
+ await this.coordinator.clear(void 0, (key) => matchesModel(key));
15305
15466
  }
15306
15467
  async dispose() {
15307
15468
  if (this.disposePromise) return await this.disposePromise;
@@ -15310,7 +15471,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15310
15471
  this.disposePromise = (async () => {
15311
15472
  await Promise.all([...this.activeLoads]);
15312
15473
  this.currentKey = void 0;
15313
- await this.cache.close?.();
15474
+ if (this.coordinator.unregister(this.cache)) await this.cache.close?.();
15314
15475
  })();
15315
15476
  await this.disposePromise;
15316
15477
  }
@@ -15324,7 +15485,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15324
15485
  if (error instanceof PPDetectionError) return error;
15325
15486
  const message = error instanceof Error ? error.message : String(error);
15326
15487
  const details = { phase, causeMessage: message };
15327
- if (/abort|cancel/i.test(message))
15488
+ if (error instanceof Error && error.name === "AbortError")
15328
15489
  return new PPDetectionError("ABORTED", "\u63A8\u7406\u5DF2\u53D6\u6D88", details, { cause: error });
15329
15490
  if (/memory|out.of.memory|allocation/i.test(message))
15330
15491
  return new PPDetectionError("OUT_OF_MEMORY", "\u8FD0\u884C\u65F6\u5185\u5B58\u4E0D\u8DB3", details, { cause: error });
@@ -15369,6 +15530,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15369
15530
  return {
15370
15531
  plan,
15371
15532
  sessionMs,
15533
+ runtimeVersion: ort.env.versions?.web ?? null,
15372
15534
  async run(feeds, runOptions = {}) {
15373
15535
  if (disposed) throw new PPDetectionError("DISPOSED", "\u4F1A\u8BDD\u5DF2\u91CA\u653E", { phase: "run" });
15374
15536
  if (runOptions.signal?.aborted)
@@ -15687,7 +15849,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15687
15849
  };
15688
15850
 
15689
15851
  // src/index.ts
15690
- var CURRENT_SDK_VERSION = "0.1.1";
15852
+ var CURRENT_SDK_VERSION = "0.2.0";
15691
15853
  function probePPDetectionCapabilities(options = {}) {
15692
15854
  return probeCapabilities(options);
15693
15855
  }
@@ -15782,6 +15944,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15782
15944
  return new URL("./inference.worker.js", (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('browser-global.js', document.baseURI).href));
15783
15945
  }
15784
15946
  async function createPPDetection(options = {}) {
15947
+ const loadStartedAt = now4();
15785
15948
  if (options.model === void 0 && options.manifest === void 0)
15786
15949
  throw new PPDetectionError("INVALID_MANIFEST", "\u521B\u5EFA PPDetection \u5B9E\u4F8B\u9700\u8981 manifest \u6216 model");
15787
15950
  const capabilities = probeCapabilities();
@@ -15814,7 +15977,6 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15814
15977
  });
15815
15978
  let executor;
15816
15979
  try {
15817
- const loadStartedAt = now4();
15818
15980
  options.onProgress?.({ phase: "model", status: "start" });
15819
15981
  let modelBytes;
15820
15982
  let actualSource;
@@ -15826,10 +15988,17 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15826
15988
  throw new PPDetectionError("MODEL_SOURCE_UNAVAILABLE", "\u6A21\u578B\u53D8\u4F53\u6CA1\u6709\u53EF\u7528\u6765\u6E90", {
15827
15989
  variantId: variant3.id
15828
15990
  });
15991
+ const integrityStartedAt = now4();
15829
15992
  await verifyModelIntegrity(memoryData, source2, options.signal);
15993
+ const integrityMs = now4() - integrityStartedAt;
15830
15994
  modelBytes = memoryData;
15831
15995
  actualSource = source2;
15832
- loadTimings = { sessionMs: 0, totalMs: now4() - loadStartedAt, integrityMs: 0 };
15996
+ loadTimings = {
15997
+ sessionMs: 0,
15998
+ totalMs: now4() - loadStartedAt,
15999
+ integrityMs,
16000
+ modelSource: "memory"
16001
+ };
15833
16002
  } else {
15834
16003
  const loaded = await modelManager.load({
15835
16004
  manifest: runtimeManifest,
@@ -15841,20 +16010,26 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15841
16010
  modelBytes = loaded.bytes;
15842
16011
  variant3 = loaded.variant;
15843
16012
  actualSource = loaded.source;
15844
- loadTimings = { ...loaded.timings, sessionMs: 0, totalMs: now4() - loadStartedAt };
16013
+ loadTimings = {
16014
+ ...loaded.timings,
16015
+ sessionMs: 0,
16016
+ totalMs: now4() - loadStartedAt,
16017
+ modelSource: loaded.fromCache ? "cache" : "network"
16018
+ };
15845
16019
  }
15846
16020
  options.onProgress?.({ phase: "model", status: "complete" });
15847
16021
  const fallbacks = [];
15848
16022
  const createExecutorForPlan = async (candidatePlan) => {
15849
16023
  options.onProgress?.({ phase: "session", status: "start" });
15850
16024
  let bridge;
16025
+ const sessionStartedAt = now4();
15851
16026
  try {
15852
16027
  if (candidatePlan.executionMode === "worker") {
15853
16028
  if (typeof Worker !== "function")
15854
16029
  throw new PPDetectionError("CAPABILITY_UNSUPPORTED", "\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301 Worker");
15855
16030
  const worker = new Worker(workerUrl(), { type: "module" });
15856
16031
  bridge = new WorkerBridge(worker);
15857
- await bridge.load(modelBytes.slice(0), candidatePlan, {
16032
+ const metadata = await bridge.load(modelBytes.slice(0), candidatePlan, {
15858
16033
  onProgress: (event) => options.onProgress?.({
15859
16034
  phase: "session",
15860
16035
  status: event.status
@@ -15864,6 +16039,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15864
16039
  numThreads: options.ort?.wasm?.numThreads
15865
16040
  }
15866
16041
  });
16042
+ runtimeVersion = typeof metadata === "object" && metadata !== null && "runtimeVersion" in metadata && typeof metadata.runtimeVersion === "string" ? metadata.runtimeVersion : null;
15867
16043
  const activeBridge = bridge;
15868
16044
  options.onProgress?.({ phase: "session", status: "complete" });
15869
16045
  return {
@@ -15881,7 +16057,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15881
16057
  wasmPaths: options.ort?.wasm?.paths,
15882
16058
  numThreads: options.ort?.wasm?.numThreads
15883
16059
  });
15884
- sessionMs = session.sessionMs;
16060
+ runtimeVersion = session.runtimeVersion ?? null;
15885
16061
  options.onProgress?.({ phase: "session", status: "complete" });
15886
16062
  return {
15887
16063
  run(input, signal) {
@@ -15905,10 +16081,13 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15905
16081
  { phase: "create", causeMessage: message },
15906
16082
  { cause: error }
15907
16083
  );
16084
+ } finally {
16085
+ sessionMs += now4() - sessionStartedAt;
15908
16086
  }
15909
16087
  };
15910
16088
  let selectedPlan = plan;
15911
16089
  let sessionMs = 0;
16090
+ let runtimeVersion = null;
15912
16091
  let selectedCandidateIndex = -1;
15913
16092
  for (const [candidateIndex, candidate] of plan.candidates.entries()) {
15914
16093
  const candidatePlan = {
@@ -15950,6 +16129,12 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15950
16129
  }
15951
16130
  if (!executor) throw new PPDetectionError("SESSION_CREATE_FAILED", "\u65E0\u6CD5\u521B\u5EFA\u68C0\u6D4B Session");
15952
16131
  const runtime = {
16132
+ runtimeVersion,
16133
+ environment: {
16134
+ userAgent: globalThis.navigator?.userAgent ?? null,
16135
+ platform: globalThis.navigator?.platform ?? null,
16136
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString()
16137
+ },
15953
16138
  requestedBackend: options.backend ?? "auto",
15954
16139
  backend: selectedPlan.actualBackend,
15955
16140
  precision: selectedPlan.actualPrecision,
@@ -15960,8 +16145,9 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15960
16145
  let activeExecutor = executor;
15961
16146
  const fallbackExecutor = {
15962
16147
  async run(input, signal) {
16148
+ const attemptInput = runtime.mode === "worker" && options.allowFallback === true && selectedCandidateIndex < plan.candidates.length - 1 ? { ...input, data: input.data.slice() } : input;
15963
16149
  try {
15964
- return await activeExecutor.run(input, signal);
16150
+ return await activeExecutor.run(attemptInput, signal);
15965
16151
  } catch (error) {
15966
16152
  if (options.allowFallback !== true || selectedCandidateIndex < 0 || selectedCandidateIndex >= plan.candidates.length - 1) {
15967
16153
  throw error;
@@ -15986,6 +16172,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15986
16172
  runtime.backend = nextPlan.actualBackend;
15987
16173
  runtime.precision = nextPlan.actualPrecision;
15988
16174
  runtime.mode = nextPlan.executionMode;
16175
+ runtime.runtimeVersion = runtimeVersion;
15989
16176
  const fallback = {
15990
16177
  cause: mapped.cause ?? mapped,
15991
16178
  code: mapped.code,
@@ -16017,6 +16204,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
16017
16204
  disposeResources: () => modelManager.dispose()
16018
16205
  });
16019
16206
  await detector.load({ signal: options.signal });
16207
+ loadTimings.totalMs = now4() - loadStartedAt;
16020
16208
  options.onProgress?.({ phase: "ready", status: "complete" });
16021
16209
  return detector;
16022
16210
  } catch (error) {
@@ -16033,8 +16221,11 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
16033
16221
  }
16034
16222
  async function clearModelCache() {
16035
16223
  const manager = new ModelManager();
16036
- await manager.clearAllCache();
16037
- await manager.dispose();
16224
+ try {
16225
+ await manager.clearAllCache();
16226
+ } finally {
16227
+ await manager.dispose();
16228
+ }
16038
16229
  }
16039
16230
 
16040
16231
  // src/browser-global.ts