web-sdk-pp-detection 0.1.0 → 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/dist/index.js CHANGED
@@ -622,7 +622,11 @@ var PPDetectionDetectorImplementation = class {
622
622
  original: { width: decoded.width, height: decoded.height }
623
623
  },
624
624
  model: this.model,
625
- runtime: this.runtime,
625
+ runtime: {
626
+ ...this.runtime,
627
+ fallbacks: this.runtime.fallbacks.map((fallback) => ({ ...fallback })),
628
+ ...this.runtime.environment ? { environment: { ...this.runtime.environment } } : {}
629
+ },
626
630
  timings: {
627
631
  decodeMs: decoded.decodeMs,
628
632
  preprocessMs,
@@ -1115,8 +1119,84 @@ function adaptModelManifest(manifest) {
1115
1119
  });
1116
1120
  }
1117
1121
 
1122
+ // src/cache/coordination.ts
1123
+ var CacheCoordinator = class {
1124
+ caches = /* @__PURE__ */ new Map();
1125
+ generation = 0;
1126
+ keys = /* @__PURE__ */ new Map();
1127
+ pending = Promise.resolve();
1128
+ guard(key) {
1129
+ const generation = this.generation;
1130
+ const keyGeneration = this.keys.get(key) ?? 0;
1131
+ this.keys.set(key, keyGeneration);
1132
+ return () => generation === this.generation && keyGeneration === (this.keys.get(key) ?? 0);
1133
+ }
1134
+ run(operation) {
1135
+ const pending = this.pending.then(operation);
1136
+ this.pending = pending.catch(() => void 0);
1137
+ return pending;
1138
+ }
1139
+ clear(key, matches) {
1140
+ if (matches) {
1141
+ for (const [candidate, generation] of this.keys) {
1142
+ if (matches(candidate)) this.keys.set(candidate, generation + 1);
1143
+ }
1144
+ } else if (key === void 0) this.generation += 1;
1145
+ else this.keys.set(key, (this.keys.get(key) ?? 0) + 1);
1146
+ return this.run(async () => {
1147
+ const outcomes = await Promise.allSettled(
1148
+ [...this.caches.keys()].map(async (cache) => {
1149
+ if (matches && cache.list) {
1150
+ for (const entry of await cache.list()) {
1151
+ if (matches(entry.key)) await cache.clearCurrent(entry.key);
1152
+ }
1153
+ } else if (key === void 0) await cache.clearAll();
1154
+ else await cache.clearCurrent(key);
1155
+ })
1156
+ );
1157
+ const failure = outcomes.find((outcome) => outcome.status === "rejected");
1158
+ if (failure?.status === "rejected") throw failure.reason;
1159
+ });
1160
+ }
1161
+ unregister(cache) {
1162
+ const count = this.caches.get(cache) ?? 0;
1163
+ if (count > 1) {
1164
+ this.caches.set(cache, count - 1);
1165
+ return false;
1166
+ }
1167
+ this.caches.delete(cache);
1168
+ return true;
1169
+ }
1170
+ };
1171
+ var coordinators = /* @__PURE__ */ new WeakMap();
1172
+ function coordinateCache(cache) {
1173
+ const scope = cache.scope ?? cache;
1174
+ let coordinator = coordinators.get(scope);
1175
+ if (!coordinator) {
1176
+ coordinator = new CacheCoordinator();
1177
+ coordinators.set(scope, coordinator);
1178
+ }
1179
+ coordinator.caches.set(cache, (coordinator.caches.get(cache) ?? 0) + 1);
1180
+ return coordinator;
1181
+ }
1182
+ var databaseScopes = /* @__PURE__ */ new WeakMap();
1183
+ function databaseScope(factory, name) {
1184
+ let scopes = databaseScopes.get(factory);
1185
+ if (!scopes) {
1186
+ scopes = /* @__PURE__ */ new Map();
1187
+ databaseScopes.set(factory, scopes);
1188
+ }
1189
+ let scope = scopes.get(name);
1190
+ if (!scope) {
1191
+ scope = {};
1192
+ scopes.set(name, scope);
1193
+ }
1194
+ return scope;
1195
+ }
1196
+
1118
1197
  // src/cache/indexeddb-cache.ts
1119
1198
  var IndexedDBModelCache = class {
1199
+ scope;
1120
1200
  factory;
1121
1201
  databaseName;
1122
1202
  database;
@@ -1125,6 +1205,7 @@ var IndexedDBModelCache = class {
1125
1205
  if (!factory) throw new Error("\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301 IndexedDB");
1126
1206
  this.factory = factory;
1127
1207
  this.databaseName = options.databaseName ?? "web-sdk-pp-detection-models-v1";
1208
+ this.scope = databaseScope(factory, this.databaseName);
1128
1209
  }
1129
1210
  async get(key) {
1130
1211
  const record3 = await this.request(
@@ -1178,6 +1259,24 @@ var IndexedDBModelCache = class {
1178
1259
  (await this.database).close();
1179
1260
  this.database = void 0;
1180
1261
  }
1262
+ async list() {
1263
+ const database = await this.open();
1264
+ return await new Promise((resolve, reject) => {
1265
+ const transaction = database.transaction("models", "readonly");
1266
+ const request = transaction.objectStore("models").openCursor();
1267
+ const entries = [];
1268
+ request.onerror = () => reject(request.error ?? new Error("\u8BFB\u53D6\u7F13\u5B58\u5BB9\u91CF\u5931\u8D25"));
1269
+ request.onsuccess = () => {
1270
+ const cursor = request.result;
1271
+ if (!cursor) return;
1272
+ const record3 = cursor.value;
1273
+ entries.push({ key: record3.key, bytes: record3.size });
1274
+ cursor.continue();
1275
+ };
1276
+ transaction.oncomplete = () => resolve(entries);
1277
+ transaction.onabort = () => reject(transaction.error ?? new Error("\u7F13\u5B58\u5BB9\u91CF\u4E8B\u52A1\u4E2D\u6B62"));
1278
+ });
1279
+ }
1181
1280
  open() {
1182
1281
  if (this.database) return this.database;
1183
1282
  this.database = new Promise((resolve, reject) => {
@@ -1247,6 +1346,11 @@ var MemoryModelCache = class {
1247
1346
  this.entries.clear();
1248
1347
  return Promise.resolve();
1249
1348
  }
1349
+ list() {
1350
+ return Promise.resolve(
1351
+ [...this.entries].map(([key, bytes]) => ({ key, bytes: bytes.byteLength }))
1352
+ );
1353
+ }
1250
1354
  };
1251
1355
 
1252
1356
  // src/cache/model-cache.ts
@@ -1254,9 +1358,11 @@ var TieredModelCache = class {
1254
1358
  constructor(memory, persistent) {
1255
1359
  this.memory = memory;
1256
1360
  this.persistent = persistent;
1361
+ this.scope = persistent?.scope ?? persistent ?? memory;
1257
1362
  }
1258
1363
  memory;
1259
1364
  persistent;
1365
+ scope;
1260
1366
  async get(key) {
1261
1367
  const memoryValue = await this.memory.get(key);
1262
1368
  if (memoryValue) return memoryValue;
@@ -1279,6 +1385,13 @@ var TieredModelCache = class {
1279
1385
  estimate() {
1280
1386
  return this.persistent?.estimate() ?? this.memory.estimate();
1281
1387
  }
1388
+ async list() {
1389
+ const entries = /* @__PURE__ */ new Map();
1390
+ for (const cache of [this.memory, this.persistent]) {
1391
+ for (const entry of await cache?.list?.() ?? []) entries.set(entry.key, entry);
1392
+ }
1393
+ return [...entries.values()];
1394
+ }
1282
1395
  async close() {
1283
1396
  await this.memory.close?.();
1284
1397
  await this.persistent?.close?.();
@@ -1488,6 +1601,14 @@ function resolveModelAsset(selection, manifest) {
1488
1601
 
1489
1602
  // src/model/model-manager.ts
1490
1603
  var SDK_CACHE_NAMESPACE = "web-sdk-pp-detection:cache-v1";
1604
+ function matchesModel(key, model) {
1605
+ try {
1606
+ const parts = JSON.parse(key);
1607
+ return Array.isArray(parts) && parts[0] === SDK_CACHE_NAMESPACE && (model === void 0 || parts[1] === model.id && parts[2] === model.version);
1608
+ } catch {
1609
+ return false;
1610
+ }
1611
+ }
1491
1612
  function now2() {
1492
1613
  return globalThis.performance?.now() ?? Date.now();
1493
1614
  }
@@ -1511,6 +1632,7 @@ function throwIfAborted4(signal) {
1511
1632
  var ModelManager = class {
1512
1633
  fetcher;
1513
1634
  cache;
1635
+ coordinator;
1514
1636
  lifecycle = new AbortController();
1515
1637
  activeLoads = /* @__PURE__ */ new Set();
1516
1638
  currentKey;
@@ -1519,6 +1641,7 @@ var ModelManager = class {
1519
1641
  constructor(options = {}) {
1520
1642
  this.fetcher = options.fetcher;
1521
1643
  this.cache = createCache(options.cache);
1644
+ this.coordinator = coordinateCache(this.cache);
1522
1645
  }
1523
1646
  cacheKey(variant3, source2, model = { id: "unknown", version: "unknown" }) {
1524
1647
  return JSON.stringify([
@@ -1557,16 +1680,24 @@ var ModelManager = class {
1557
1680
  const variant3 = resolveModelVariant(manifest, options.variantId);
1558
1681
  const sourceKind2 = options.sourceKind ?? "auto";
1559
1682
  const sources = resolveModelSources(variant3, sourceKind2);
1683
+ const guards = new Map(
1684
+ sources.map((source2) => {
1685
+ const key = this.cacheKey(variant3, source2, manifest.model);
1686
+ return [key, this.coordinator.guard(key)];
1687
+ })
1688
+ );
1560
1689
  const failures = [];
1561
1690
  let lastError;
1562
1691
  for (const source2 of sources) {
1563
1692
  throwIfAborted4(options.signal);
1564
1693
  const asset = { model: manifest.model, variant: variant3, source: source2 };
1565
1694
  const cacheKey = this.cacheKey(variant3, source2, manifest.model);
1695
+ this.currentKey = cacheKey;
1696
+ const canMutate = guards.get(cacheKey);
1566
1697
  const cacheStarted = now2();
1567
1698
  let cached;
1568
1699
  try {
1569
- cached = await this.cache.get(cacheKey);
1700
+ cached = await this.coordinator.run(() => this.cache.get(cacheKey));
1570
1701
  } catch (error) {
1571
1702
  if (error instanceof PPDetectionError && error.code === "ABORTED") throw error;
1572
1703
  throwIfAborted4(options.signal);
@@ -1577,7 +1708,6 @@ var ModelManager = class {
1577
1708
  const integrityStarted = now2();
1578
1709
  try {
1579
1710
  await verifyModelIntegrity(cached, source2, options.signal);
1580
- this.currentKey = cacheKey;
1581
1711
  return {
1582
1712
  bytes: cached,
1583
1713
  manifest,
@@ -1591,7 +1721,9 @@ var ModelManager = class {
1591
1721
  } catch (error) {
1592
1722
  if (error instanceof PPDetectionError && error.code === "ABORTED") throw error;
1593
1723
  try {
1594
- await this.cache.clearCurrent(cacheKey);
1724
+ await this.coordinator.run(async () => {
1725
+ if (canMutate()) await this.cache.clearCurrent(cacheKey);
1726
+ });
1595
1727
  } catch (clearError) {
1596
1728
  if (clearError instanceof PPDetectionError && clearError.code === "ABORTED")
1597
1729
  throw clearError;
@@ -1627,11 +1759,12 @@ var ModelManager = class {
1627
1759
  }
1628
1760
  throwIfAborted4(options.signal);
1629
1761
  try {
1630
- await this.cache.put(cacheKey, loaded.bytes);
1762
+ await this.coordinator.run(async () => {
1763
+ if (canMutate()) await this.cache.put(cacheKey, loaded.bytes);
1764
+ });
1631
1765
  } catch {
1632
1766
  }
1633
1767
  throwIfAborted4(options.signal);
1634
- this.currentKey = cacheKey;
1635
1768
  return {
1636
1769
  bytes: loaded.bytes,
1637
1770
  manifest,
@@ -1656,16 +1789,44 @@ var ModelManager = class {
1656
1789
  estimate() {
1657
1790
  return this.getCacheEstimate();
1658
1791
  }
1659
- getCacheEstimate() {
1660
- return this.cache.estimate();
1792
+ getCacheEstimate(model) {
1793
+ return this.coordinator.run(async () => {
1794
+ if (!this.cache.list) {
1795
+ if (model)
1796
+ throw new PPDetectionError(
1797
+ "CAPABILITY_UNSUPPORTED",
1798
+ "\u81EA\u5B9A\u4E49\u7F13\u5B58\u9700\u5B9E\u73B0 list \u624D\u80FD\u6309\u6A21\u578B\u4F30\u7B97"
1799
+ );
1800
+ return this.cache.estimate();
1801
+ }
1802
+ const unique = /* @__PURE__ */ new Map();
1803
+ for (const cache of this.coordinator.caches.keys()) {
1804
+ for (const entry of await cache.list?.() ?? []) unique.set(entry.key, entry);
1805
+ }
1806
+ const entries = [...unique.values()].filter((entry) => matchesModel(entry.key, model));
1807
+ return {
1808
+ bytes: entries.reduce((sum, entry) => sum + entry.bytes, 0),
1809
+ entries: entries.length
1810
+ };
1811
+ });
1661
1812
  }
1662
- async clearCurrentModelCache() {
1813
+ async clearCurrentModelCache(model) {
1814
+ if (model) {
1815
+ if (!model.id || !model.version)
1816
+ throw new PPDetectionError("INVALID_MANIFEST", "\u7F13\u5B58\u6A21\u578B\u8EAB\u4EFD\u5FC5\u987B\u5305\u542B id \u548C version");
1817
+ if (!this.cache.list)
1818
+ throw new PPDetectionError(
1819
+ "CAPABILITY_UNSUPPORTED",
1820
+ "\u81EA\u5B9A\u4E49\u7F13\u5B58\u9700\u5B9E\u73B0 list \u624D\u80FD\u6309\u6A21\u578B\u6E05\u7406"
1821
+ );
1822
+ await this.coordinator.clear(void 0, (key) => matchesModel(key, model));
1823
+ return;
1824
+ }
1663
1825
  if (!this.currentKey) return;
1664
- await this.cache.clearCurrent(this.currentKey);
1826
+ await this.coordinator.clear(this.currentKey);
1665
1827
  }
1666
1828
  async clearAllCache() {
1667
- await this.cache.clearAll();
1668
- this.currentKey = void 0;
1829
+ await this.coordinator.clear(void 0, (key) => matchesModel(key));
1669
1830
  }
1670
1831
  async dispose() {
1671
1832
  if (this.disposePromise) return await this.disposePromise;
@@ -1674,7 +1835,7 @@ var ModelManager = class {
1674
1835
  this.disposePromise = (async () => {
1675
1836
  await Promise.all([...this.activeLoads]);
1676
1837
  this.currentKey = void 0;
1677
- await this.cache.close?.();
1838
+ if (this.coordinator.unregister(this.cache)) await this.cache.close?.();
1678
1839
  })();
1679
1840
  await this.disposePromise;
1680
1841
  }
@@ -1687,14 +1848,15 @@ function now3() {
1687
1848
  function mapError(error, phase) {
1688
1849
  if (error instanceof PPDetectionError) return error;
1689
1850
  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 });
1851
+ const details = { phase, causeMessage: message };
1852
+ if (error instanceof Error && error.name === "AbortError")
1853
+ return new PPDetectionError("ABORTED", "\u63A8\u7406\u5DF2\u53D6\u6D88", details, { cause: error });
1692
1854
  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 });
1855
+ return new PPDetectionError("OUT_OF_MEMORY", "\u8FD0\u884C\u65F6\u5185\u5B58\u4E0D\u8DB3", details, { cause: error });
1694
1856
  return new PPDetectionError(
1695
1857
  phase === "create" ? "SESSION_CREATE_FAILED" : "INFERENCE_FAILED",
1696
1858
  phase === "create" ? "\u521B\u5EFA ONNX Runtime \u4F1A\u8BDD\u5931\u8D25" : "ONNX Runtime \u63A8\u7406\u5931\u8D25",
1697
- { phase },
1859
+ details,
1698
1860
  { cause: error }
1699
1861
  );
1700
1862
  }
@@ -1732,6 +1894,7 @@ async function createOrtSession(modelBytes, plan, options = {}) {
1732
1894
  return {
1733
1895
  plan,
1734
1896
  sessionMs,
1897
+ runtimeVersion: ort.env.versions?.web ?? null,
1735
1898
  async run(feeds, runOptions = {}) {
1736
1899
  if (disposed) throw new PPDetectionError("DISPOSED", "\u4F1A\u8BDD\u5DF2\u91CA\u653E", { phase: "run" });
1737
1900
  if (runOptions.signal?.aborted)
@@ -1852,14 +2015,13 @@ function probeCapabilities(options = {}) {
1852
2015
  function fail(code, message, details) {
1853
2016
  throw new PPDetectionError(code, message, details);
1854
2017
  }
1855
- function candidatesForBackend(requested, capabilities, allowFallback) {
2018
+ function candidatesForBackend(requested, capabilities) {
1856
2019
  if (requested === "wasm") return ["wasm"];
1857
2020
  if (requested === "webgpu")
1858
2021
  return capabilities.webgpu ? ["webgpu"] : fail("CAPABILITY_UNSUPPORTED", "\u8BF7\u6C42\u7684 webgpu \u4E0D\u53EF\u7528", { requestedBackend: requested });
1859
2022
  const available = [];
1860
2023
  if (capabilities.webgpu) available.push("webgpu");
1861
2024
  available.push("wasm");
1862
- if (!allowFallback) return available.slice(0, 1);
1863
2025
  return available;
1864
2026
  }
1865
2027
  function selectExecutionPlan(options, capabilities, manifest) {
@@ -1877,19 +2039,18 @@ function selectExecutionPlan(options, capabilities, manifest) {
1877
2039
  requestedPrecision
1878
2040
  });
1879
2041
  }
1880
- const candidates = candidatesForBackend(
1881
- requestedBackend,
1882
- capabilities,
1883
- options.allowFallback === true
1884
- ).filter((backend) => variant3.backends.includes(backend));
1885
- if (candidates.length === 0) {
2042
+ const candidates = candidatesForBackend(requestedBackend, capabilities).filter(
2043
+ (backend) => variant3.backends.includes(backend)
2044
+ );
2045
+ const selectedCandidates = options.allowFallback === true ? candidates : candidates.slice(0, 1);
2046
+ if (selectedCandidates.length === 0) {
1886
2047
  fail("CAPABILITY_UNSUPPORTED", "\u6CA1\u6709\u4E0E\u6A21\u578B\u53D8\u4F53\u5339\u914D\u7684\u53EF\u7528\u540E\u7AEF", {
1887
2048
  requestedBackend,
1888
2049
  requestedPrecision,
1889
2050
  availableBackends: variant3.backends
1890
2051
  });
1891
2052
  }
1892
- const actualBackend = candidates[0];
2053
+ const actualBackend = selectedCandidates[0];
1893
2054
  return {
1894
2055
  variantId: variant3.id,
1895
2056
  requestedBackend,
@@ -1897,7 +2058,7 @@ function selectExecutionPlan(options, capabilities, manifest) {
1897
2058
  requestedPrecision,
1898
2059
  actualPrecision: variant3.precision,
1899
2060
  executionMode,
1900
- candidates: candidates.map((backend) => ({
2061
+ candidates: selectedCandidates.map((backend) => ({
1901
2062
  variantId: variant3.id,
1902
2063
  backend,
1903
2064
  precision: variant3.precision,
@@ -2052,7 +2213,7 @@ var WorkerBridge = class {
2052
2213
  };
2053
2214
 
2054
2215
  // src/index.ts
2055
- var CURRENT_SDK_VERSION = "0.1.0";
2216
+ var CURRENT_SDK_VERSION = "0.2.0";
2056
2217
  function probePPDetectionCapabilities(options = {}) {
2057
2218
  return probeCapabilities(options);
2058
2219
  }
@@ -2147,6 +2308,7 @@ function workerUrl() {
2147
2308
  return new URL("./inference.worker.js", import.meta.url);
2148
2309
  }
2149
2310
  async function createPPDetection(options = {}) {
2311
+ const loadStartedAt = now4();
2150
2312
  if (options.model === void 0 && options.manifest === void 0)
2151
2313
  throw new PPDetectionError("INVALID_MANIFEST", "\u521B\u5EFA PPDetection \u5B9E\u4F8B\u9700\u8981 manifest \u6216 model");
2152
2314
  const capabilities = probeCapabilities();
@@ -2178,9 +2340,7 @@ async function createPPDetection(options = {}) {
2178
2340
  cache: options.cache === false ? false : options.cache === "memory" ? "memory" : void 0
2179
2341
  });
2180
2342
  let executor;
2181
- let activeBridge;
2182
2343
  try {
2183
- const loadStartedAt = now4();
2184
2344
  options.onProgress?.({ phase: "model", status: "start" });
2185
2345
  let modelBytes;
2186
2346
  let actualSource;
@@ -2192,10 +2352,17 @@ async function createPPDetection(options = {}) {
2192
2352
  throw new PPDetectionError("MODEL_SOURCE_UNAVAILABLE", "\u6A21\u578B\u53D8\u4F53\u6CA1\u6709\u53EF\u7528\u6765\u6E90", {
2193
2353
  variantId: variant3.id
2194
2354
  });
2355
+ const integrityStartedAt = now4();
2195
2356
  await verifyModelIntegrity(memoryData, source2, options.signal);
2357
+ const integrityMs = now4() - integrityStartedAt;
2196
2358
  modelBytes = memoryData;
2197
2359
  actualSource = source2;
2198
- loadTimings = { sessionMs: 0, totalMs: now4() - loadStartedAt, integrityMs: 0 };
2360
+ loadTimings = {
2361
+ sessionMs: 0,
2362
+ totalMs: now4() - loadStartedAt,
2363
+ integrityMs,
2364
+ modelSource: "memory"
2365
+ };
2199
2366
  } else {
2200
2367
  const loaded = await modelManager.load({
2201
2368
  manifest: runtimeManifest,
@@ -2207,33 +2374,26 @@ async function createPPDetection(options = {}) {
2207
2374
  modelBytes = loaded.bytes;
2208
2375
  variant3 = loaded.variant;
2209
2376
  actualSource = loaded.source;
2210
- loadTimings = { ...loaded.timings, sessionMs: 0, totalMs: now4() - loadStartedAt };
2377
+ loadTimings = {
2378
+ ...loaded.timings,
2379
+ sessionMs: 0,
2380
+ totalMs: now4() - loadStartedAt,
2381
+ modelSource: loaded.fromCache ? "cache" : "network"
2382
+ };
2211
2383
  }
2212
2384
  options.onProgress?.({ phase: "model", status: "complete" });
2213
2385
  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
- };
2386
+ const createExecutorForPlan = async (candidatePlan) => {
2225
2387
  options.onProgress?.({ phase: "session", status: "start" });
2388
+ let bridge;
2389
+ const sessionStartedAt = now4();
2226
2390
  try {
2227
- if (candidate.executionMode === "worker") {
2391
+ if (candidatePlan.executionMode === "worker") {
2228
2392
  if (typeof Worker !== "function")
2229
2393
  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, {
2394
+ const worker = new Worker(workerUrl(), { type: "module" });
2395
+ bridge = new WorkerBridge(worker);
2396
+ const metadata = await bridge.load(modelBytes.slice(0), candidatePlan, {
2237
2397
  onProgress: (event) => options.onProgress?.({
2238
2398
  phase: "session",
2239
2399
  status: event.status
@@ -2243,35 +2403,69 @@ async function createPPDetection(options = {}) {
2243
2403
  numThreads: options.ort?.wasm?.numThreads
2244
2404
  }
2245
2405
  });
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 = {
2406
+ runtimeVersion = typeof metadata === "object" && metadata !== null && "runtimeVersion" in metadata && typeof metadata.runtimeVersion === "string" ? metadata.runtimeVersion : null;
2407
+ const activeBridge = bridge;
2408
+ options.onProgress?.({ phase: "session", status: "complete" });
2409
+ return {
2264
2410
  run(input, signal) {
2265
- return session.run(
2411
+ return activeBridge.run(
2266
2412
  { [input.inputName]: { data: input.data, dims: input.dims } },
2267
2413
  { signal }
2268
2414
  );
2269
2415
  },
2270
- dispose: () => session.dispose()
2416
+ dispose: () => activeBridge.dispose()
2271
2417
  };
2272
2418
  }
2273
- selectedPlan = candidatePlan;
2419
+ const session = await createOrtSession(modelBytes, candidatePlan, {
2420
+ ort: options.ort?.module,
2421
+ wasmPaths: options.ort?.wasm?.paths,
2422
+ numThreads: options.ort?.wasm?.numThreads
2423
+ });
2424
+ runtimeVersion = session.runtimeVersion ?? null;
2274
2425
  options.onProgress?.({ phase: "session", status: "complete" });
2426
+ return {
2427
+ run(input, signal) {
2428
+ return session.run(
2429
+ { [input.inputName]: { data: input.data, dims: input.dims } },
2430
+ { signal }
2431
+ );
2432
+ },
2433
+ dispose: () => session.dispose()
2434
+ };
2435
+ } catch (error) {
2436
+ try {
2437
+ await bridge?.dispose();
2438
+ } catch {
2439
+ }
2440
+ if (error instanceof PPDetectionError) throw error;
2441
+ const message = error instanceof Error ? error.message : String(error);
2442
+ throw new PPDetectionError(
2443
+ "SESSION_CREATE_FAILED",
2444
+ "\u521B\u5EFA ONNX Runtime \u4F1A\u8BDD\u5931\u8D25",
2445
+ { phase: "create", causeMessage: message },
2446
+ { cause: error }
2447
+ );
2448
+ } finally {
2449
+ sessionMs += now4() - sessionStartedAt;
2450
+ }
2451
+ };
2452
+ let selectedPlan = plan;
2453
+ let sessionMs = 0;
2454
+ let runtimeVersion = null;
2455
+ let selectedCandidateIndex = -1;
2456
+ for (const [candidateIndex, candidate] of plan.candidates.entries()) {
2457
+ const candidatePlan = {
2458
+ ...plan,
2459
+ variantId: candidate.variantId,
2460
+ actualBackend: candidate.backend,
2461
+ actualPrecision: candidate.precision,
2462
+ executionMode: candidate.executionMode,
2463
+ candidates: [candidate]
2464
+ };
2465
+ try {
2466
+ executor = await createExecutorForPlan(candidatePlan);
2467
+ selectedPlan = candidatePlan;
2468
+ selectedCandidateIndex = candidateIndex;
2275
2469
  break;
2276
2470
  } catch (error) {
2277
2471
  const mapped = error instanceof PPDetectionError ? error : new PPDetectionError("SESSION_CREATE_FAILED", String(error));
@@ -2281,11 +2475,6 @@ async function createPPDetection(options = {}) {
2281
2475
  } catch {
2282
2476
  }
2283
2477
  executor = void 0;
2284
- try {
2285
- await activeBridge?.dispose();
2286
- } catch {
2287
- }
2288
- activeBridge = void 0;
2289
2478
  if (!hasNext) {
2290
2479
  throw mapped;
2291
2480
  }
@@ -2303,22 +2492,75 @@ async function createPPDetection(options = {}) {
2303
2492
  }
2304
2493
  }
2305
2494
  if (!executor) throw new PPDetectionError("SESSION_CREATE_FAILED", "\u65E0\u6CD5\u521B\u5EFA\u68C0\u6D4B Session");
2306
- const loadedExecutor = executor;
2495
+ const runtime = {
2496
+ runtimeVersion,
2497
+ environment: {
2498
+ userAgent: globalThis.navigator?.userAgent ?? null,
2499
+ platform: globalThis.navigator?.platform ?? null,
2500
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString()
2501
+ },
2502
+ requestedBackend: options.backend ?? "auto",
2503
+ backend: selectedPlan.actualBackend,
2504
+ precision: selectedPlan.actualPrecision,
2505
+ mode: selectedPlan.executionMode,
2506
+ fallbacks,
2507
+ capabilities
2508
+ };
2509
+ let activeExecutor = executor;
2510
+ const fallbackExecutor = {
2511
+ async run(input, signal) {
2512
+ const attemptInput = runtime.mode === "worker" && options.allowFallback === true && selectedCandidateIndex < plan.candidates.length - 1 ? { ...input, data: input.data.slice() } : input;
2513
+ try {
2514
+ return await activeExecutor.run(attemptInput, signal);
2515
+ } catch (error) {
2516
+ if (options.allowFallback !== true || selectedCandidateIndex < 0 || selectedCandidateIndex >= plan.candidates.length - 1) {
2517
+ throw error;
2518
+ }
2519
+ const failedCandidate = plan.candidates[selectedCandidateIndex];
2520
+ const mapped = error instanceof PPDetectionError ? error : new PPDetectionError("INFERENCE_FAILED", String(error), {}, { cause: error });
2521
+ if (mapped.code === "ABORTED") throw mapped;
2522
+ const nextCandidate = plan.candidates[selectedCandidateIndex + 1];
2523
+ const nextPlan = {
2524
+ ...plan,
2525
+ variantId: nextCandidate.variantId,
2526
+ actualBackend: nextCandidate.backend,
2527
+ actualPrecision: nextCandidate.precision,
2528
+ executionMode: nextCandidate.executionMode,
2529
+ candidates: [nextCandidate]
2530
+ };
2531
+ await activeExecutor.dispose();
2532
+ const nextExecutor = await createExecutorForPlan(nextPlan);
2533
+ activeExecutor = nextExecutor;
2534
+ selectedCandidateIndex += 1;
2535
+ selectedPlan = nextPlan;
2536
+ runtime.backend = nextPlan.actualBackend;
2537
+ runtime.precision = nextPlan.actualPrecision;
2538
+ runtime.mode = nextPlan.executionMode;
2539
+ runtime.runtimeVersion = runtimeVersion;
2540
+ const fallback = {
2541
+ cause: mapped.cause ?? mapped,
2542
+ code: mapped.code,
2543
+ message: mapped.message,
2544
+ precision: failedCandidate.precision,
2545
+ provider: failedCandidate.backend,
2546
+ stage: "inference",
2547
+ variantId: failedCandidate.variantId
2548
+ };
2549
+ fallbacks.push(fallback);
2550
+ options.onProgress?.({ phase: "fallback", status: "complete", fallback });
2551
+ return activeExecutor.run(input, signal);
2552
+ }
2553
+ },
2554
+ dispose: () => activeExecutor.dispose()
2555
+ };
2307
2556
  loadTimings = { ...loadTimings, sessionMs, totalMs: now4() - loadStartedAt };
2308
2557
  const detector = new PPDetectionDetectorImplementation({
2309
2558
  capabilities,
2310
2559
  manifest: runtimeManifest,
2311
2560
  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
- },
2561
+ runtime,
2320
2562
  loadTimings,
2321
- loadExecutor: () => Promise.resolve(loadedExecutor),
2563
+ loadExecutor: () => Promise.resolve(fallbackExecutor),
2322
2564
  onProgress: options.onProgress,
2323
2565
  clearCurrentModelCache: () => modelManager.clearCurrentModelCache(),
2324
2566
  clearAllCache: () => modelManager.clearAllCache(),
@@ -2326,6 +2568,7 @@ async function createPPDetection(options = {}) {
2326
2568
  disposeResources: () => modelManager.dispose()
2327
2569
  });
2328
2570
  await detector.load({ signal: options.signal });
2571
+ loadTimings.totalMs = now4() - loadStartedAt;
2329
2572
  options.onProgress?.({ phase: "ready", status: "complete" });
2330
2573
  return detector;
2331
2574
  } catch (error) {
@@ -2333,10 +2576,6 @@ async function createPPDetection(options = {}) {
2333
2576
  await executor?.dispose();
2334
2577
  } catch {
2335
2578
  }
2336
- try {
2337
- await activeBridge?.dispose();
2338
- } catch {
2339
- }
2340
2579
  try {
2341
2580
  await modelManager.dispose();
2342
2581
  } catch {
@@ -2346,8 +2585,11 @@ async function createPPDetection(options = {}) {
2346
2585
  }
2347
2586
  async function clearModelCache() {
2348
2587
  const manager = new ModelManager();
2349
- await manager.clearAllCache();
2350
- await manager.dispose();
2588
+ try {
2589
+ await manager.clearAllCache();
2590
+ } finally {
2591
+ await manager.dispose();
2592
+ }
2351
2593
  }
2352
2594
 
2353
2595
  export { CURRENT_SDK_VERSION, IndexedDBModelCache, MemoryModelCache, ModelManager, PPDetectionError, adaptModelManifest, clearModelCache, createOrtSession, createPPDetection, loadModelAsset, parseDetectionManifest, parseModelManifest, probeCapabilities, probePPDetectionCapabilities, resolveModelAsset, selectExecutionPlan };