edgeflowjs 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.
Files changed (98) hide show
  1. package/README.md +473 -0
  2. package/dist/backends/index.d.ts +13 -0
  3. package/dist/backends/index.d.ts.map +1 -0
  4. package/dist/backends/index.js +32 -0
  5. package/dist/backends/index.js.map +1 -0
  6. package/dist/backends/onnx.d.ts +46 -0
  7. package/dist/backends/onnx.d.ts.map +1 -0
  8. package/dist/backends/onnx.js +249 -0
  9. package/dist/backends/onnx.js.map +1 -0
  10. package/dist/backends/wasm.d.ts +78 -0
  11. package/dist/backends/wasm.d.ts.map +1 -0
  12. package/dist/backends/wasm.js +358 -0
  13. package/dist/backends/wasm.js.map +1 -0
  14. package/dist/backends/webgpu.d.ts +143 -0
  15. package/dist/backends/webgpu.d.ts.map +1 -0
  16. package/dist/backends/webgpu.js +326 -0
  17. package/dist/backends/webgpu.js.map +1 -0
  18. package/dist/backends/webnn.d.ts +115 -0
  19. package/dist/backends/webnn.d.ts.map +1 -0
  20. package/dist/backends/webnn.js +202 -0
  21. package/dist/backends/webnn.js.map +1 -0
  22. package/dist/core/index.d.ts +9 -0
  23. package/dist/core/index.d.ts.map +1 -0
  24. package/dist/core/index.js +14 -0
  25. package/dist/core/index.js.map +1 -0
  26. package/dist/core/memory.d.ts +234 -0
  27. package/dist/core/memory.d.ts.map +1 -0
  28. package/dist/core/memory.js +554 -0
  29. package/dist/core/memory.js.map +1 -0
  30. package/dist/core/runtime.d.ts +129 -0
  31. package/dist/core/runtime.d.ts.map +1 -0
  32. package/dist/core/runtime.js +352 -0
  33. package/dist/core/runtime.js.map +1 -0
  34. package/dist/core/scheduler.d.ts +118 -0
  35. package/dist/core/scheduler.d.ts.map +1 -0
  36. package/dist/core/scheduler.js +600 -0
  37. package/dist/core/scheduler.js.map +1 -0
  38. package/dist/core/tensor.d.ts +149 -0
  39. package/dist/core/tensor.d.ts.map +1 -0
  40. package/dist/core/tensor.js +719 -0
  41. package/dist/core/tensor.js.map +1 -0
  42. package/dist/core/types.d.ts +367 -0
  43. package/dist/core/types.d.ts.map +1 -0
  44. package/dist/core/types.js +54 -0
  45. package/dist/core/types.js.map +1 -0
  46. package/dist/edgeflow.browser.js +5601 -0
  47. package/dist/edgeflow.browser.js.map +7 -0
  48. package/dist/edgeflow.browser.min.js +19 -0
  49. package/dist/edgeflow.browser.min.js.map +7 -0
  50. package/dist/index.d.ts +71 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +158 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/pipelines/base.d.ts +122 -0
  55. package/dist/pipelines/base.d.ts.map +1 -0
  56. package/dist/pipelines/base.js +155 -0
  57. package/dist/pipelines/base.js.map +1 -0
  58. package/dist/pipelines/feature-extraction.d.ts +68 -0
  59. package/dist/pipelines/feature-extraction.d.ts.map +1 -0
  60. package/dist/pipelines/feature-extraction.js +197 -0
  61. package/dist/pipelines/feature-extraction.js.map +1 -0
  62. package/dist/pipelines/image-classification.d.ts +61 -0
  63. package/dist/pipelines/image-classification.d.ts.map +1 -0
  64. package/dist/pipelines/image-classification.js +140 -0
  65. package/dist/pipelines/image-classification.js.map +1 -0
  66. package/dist/pipelines/index.d.ts +58 -0
  67. package/dist/pipelines/index.d.ts.map +1 -0
  68. package/dist/pipelines/index.js +72 -0
  69. package/dist/pipelines/index.js.map +1 -0
  70. package/dist/pipelines/text-classification.d.ts +71 -0
  71. package/dist/pipelines/text-classification.d.ts.map +1 -0
  72. package/dist/pipelines/text-classification.js +175 -0
  73. package/dist/pipelines/text-classification.js.map +1 -0
  74. package/dist/tools/index.d.ts +143 -0
  75. package/dist/tools/index.d.ts.map +1 -0
  76. package/dist/tools/index.js +294 -0
  77. package/dist/tools/index.js.map +1 -0
  78. package/dist/utils/cache.d.ts +162 -0
  79. package/dist/utils/cache.d.ts.map +1 -0
  80. package/dist/utils/cache.js +443 -0
  81. package/dist/utils/cache.js.map +1 -0
  82. package/dist/utils/index.d.ts +8 -0
  83. package/dist/utils/index.d.ts.map +1 -0
  84. package/dist/utils/index.js +12 -0
  85. package/dist/utils/index.js.map +1 -0
  86. package/dist/utils/model-loader.d.ts +107 -0
  87. package/dist/utils/model-loader.d.ts.map +1 -0
  88. package/dist/utils/model-loader.js +694 -0
  89. package/dist/utils/model-loader.js.map +1 -0
  90. package/dist/utils/preprocessor.d.ts +147 -0
  91. package/dist/utils/preprocessor.d.ts.map +1 -0
  92. package/dist/utils/preprocessor.js +423 -0
  93. package/dist/utils/preprocessor.js.map +1 -0
  94. package/dist/utils/tokenizer.d.ts +140 -0
  95. package/dist/utils/tokenizer.d.ts.map +1 -0
  96. package/dist/utils/tokenizer.js +397 -0
  97. package/dist/utils/tokenizer.js.map +1 -0
  98. package/package.json +87 -0
@@ -0,0 +1,694 @@
1
+ /**
2
+ * edgeFlow.js - Advanced Model Loader
3
+ *
4
+ * Features:
5
+ * - Preloading: Background model loading
6
+ * - Sharding: Split large files into chunks for download
7
+ * - Resume Download: Continue download from where it left off
8
+ * - Model Caching: IndexedDB storage for large models
9
+ */
10
+ // ============================================================================
11
+ // IndexedDB Model Cache
12
+ // ============================================================================
13
+ const DB_NAME = 'edgeflow-model-cache';
14
+ const DB_VERSION = 1;
15
+ const STORE_META = 'meta';
16
+ const STORE_CHUNKS = 'chunks';
17
+ const STORE_STATE = 'download-state';
18
+ /**
19
+ * IndexedDB-based model cache for large files
20
+ */
21
+ class ModelCache {
22
+ db = null;
23
+ dbPromise = null;
24
+ /**
25
+ * Open the database
26
+ */
27
+ async openDB() {
28
+ if (this.db)
29
+ return this.db;
30
+ if (this.dbPromise)
31
+ return this.dbPromise;
32
+ this.dbPromise = new Promise((resolve, reject) => {
33
+ const request = indexedDB.open(DB_NAME, DB_VERSION);
34
+ request.onupgradeneeded = (event) => {
35
+ const db = event.target.result;
36
+ // Model metadata store
37
+ if (!db.objectStoreNames.contains(STORE_META)) {
38
+ db.createObjectStore(STORE_META, { keyPath: 'url' });
39
+ }
40
+ // Chunk data store
41
+ if (!db.objectStoreNames.contains(STORE_CHUNKS)) {
42
+ const chunkStore = db.createObjectStore(STORE_CHUNKS, { keyPath: ['url', 'index'] });
43
+ chunkStore.createIndex('url', 'url', { unique: false });
44
+ }
45
+ // Download state store (for resume)
46
+ if (!db.objectStoreNames.contains(STORE_STATE)) {
47
+ db.createObjectStore(STORE_STATE, { keyPath: 'url' });
48
+ }
49
+ };
50
+ request.onsuccess = () => {
51
+ this.db = request.result;
52
+ resolve(this.db);
53
+ };
54
+ request.onerror = () => reject(request.error);
55
+ });
56
+ return this.dbPromise;
57
+ }
58
+ /**
59
+ * Get cached model metadata
60
+ */
61
+ async getMeta(url) {
62
+ const db = await this.openDB();
63
+ return new Promise((resolve, reject) => {
64
+ const tx = db.transaction(STORE_META, 'readonly');
65
+ const store = tx.objectStore(STORE_META);
66
+ const request = store.get(url);
67
+ request.onsuccess = () => resolve(request.result ?? null);
68
+ request.onerror = () => reject(request.error);
69
+ });
70
+ }
71
+ /**
72
+ * Save model metadata
73
+ */
74
+ async saveMeta(meta) {
75
+ const db = await this.openDB();
76
+ return new Promise((resolve, reject) => {
77
+ const tx = db.transaction(STORE_META, 'readwrite');
78
+ const store = tx.objectStore(STORE_META);
79
+ store.put(meta);
80
+ tx.oncomplete = () => resolve();
81
+ tx.onerror = () => reject(tx.error);
82
+ });
83
+ }
84
+ /**
85
+ * Save a chunk
86
+ */
87
+ async saveChunk(url, index, data) {
88
+ const db = await this.openDB();
89
+ return new Promise((resolve, reject) => {
90
+ const tx = db.transaction(STORE_CHUNKS, 'readwrite');
91
+ const store = tx.objectStore(STORE_CHUNKS);
92
+ store.put({ url, index, data });
93
+ tx.oncomplete = () => resolve();
94
+ tx.onerror = () => reject(tx.error);
95
+ });
96
+ }
97
+ /**
98
+ * Get all chunks for a URL
99
+ */
100
+ async getChunks(url) {
101
+ const db = await this.openDB();
102
+ return new Promise((resolve, reject) => {
103
+ const tx = db.transaction(STORE_CHUNKS, 'readonly');
104
+ const store = tx.objectStore(STORE_CHUNKS);
105
+ const index = store.index('url');
106
+ const request = index.getAll(url);
107
+ request.onsuccess = () => {
108
+ const results = request.result;
109
+ // Sort by index and extract data
110
+ results.sort((a, b) => a.index - b.index);
111
+ resolve(results.map(r => r.data));
112
+ };
113
+ request.onerror = () => reject(request.error);
114
+ });
115
+ }
116
+ /**
117
+ * Get complete model data (merged chunks)
118
+ */
119
+ async getModel(url) {
120
+ const meta = await this.getMeta(url);
121
+ if (!meta || !meta.complete)
122
+ return null;
123
+ const chunks = await this.getChunks(url);
124
+ if (chunks.length === 0)
125
+ return null;
126
+ // Merge chunks
127
+ const totalSize = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
128
+ const result = new Uint8Array(totalSize);
129
+ let offset = 0;
130
+ for (const chunk of chunks) {
131
+ result.set(new Uint8Array(chunk), offset);
132
+ offset += chunk.byteLength;
133
+ }
134
+ return result.buffer;
135
+ }
136
+ /**
137
+ * Save download state (for resume)
138
+ */
139
+ async saveDownloadState(state) {
140
+ const db = await this.openDB();
141
+ return new Promise((resolve, reject) => {
142
+ const tx = db.transaction(STORE_STATE, 'readwrite');
143
+ const store = tx.objectStore(STORE_STATE);
144
+ store.put(state);
145
+ tx.oncomplete = () => resolve();
146
+ tx.onerror = () => reject(tx.error);
147
+ });
148
+ }
149
+ /**
150
+ * Get download state
151
+ */
152
+ async getDownloadState(url) {
153
+ const db = await this.openDB();
154
+ return new Promise((resolve, reject) => {
155
+ const tx = db.transaction(STORE_STATE, 'readonly');
156
+ const store = tx.objectStore(STORE_STATE);
157
+ const request = store.get(url);
158
+ request.onsuccess = () => resolve(request.result ?? null);
159
+ request.onerror = () => reject(request.error);
160
+ });
161
+ }
162
+ /**
163
+ * Delete download state
164
+ */
165
+ async deleteDownloadState(url) {
166
+ const db = await this.openDB();
167
+ return new Promise((resolve, reject) => {
168
+ const tx = db.transaction(STORE_STATE, 'readwrite');
169
+ const store = tx.objectStore(STORE_STATE);
170
+ store.delete(url);
171
+ tx.oncomplete = () => resolve();
172
+ tx.onerror = () => reject(tx.error);
173
+ });
174
+ }
175
+ /**
176
+ * Delete cached model
177
+ */
178
+ async deleteModel(url) {
179
+ const db = await this.openDB();
180
+ // Delete metadata
181
+ await new Promise((resolve, reject) => {
182
+ const tx = db.transaction(STORE_META, 'readwrite');
183
+ const store = tx.objectStore(STORE_META);
184
+ store.delete(url);
185
+ tx.oncomplete = () => resolve();
186
+ tx.onerror = () => reject(tx.error);
187
+ });
188
+ // Delete chunks
189
+ const chunks = await this.getChunks(url);
190
+ if (chunks.length > 0) {
191
+ await new Promise((resolve, reject) => {
192
+ const tx = db.transaction(STORE_CHUNKS, 'readwrite');
193
+ const store = tx.objectStore(STORE_CHUNKS);
194
+ const index = store.index('url');
195
+ const request = index.openCursor(IDBKeyRange.only(url));
196
+ request.onsuccess = (event) => {
197
+ const cursor = event.target.result;
198
+ if (cursor) {
199
+ cursor.delete();
200
+ cursor.continue();
201
+ }
202
+ };
203
+ tx.oncomplete = () => resolve();
204
+ tx.onerror = () => reject(tx.error);
205
+ });
206
+ }
207
+ // Delete download state
208
+ await this.deleteDownloadState(url);
209
+ }
210
+ /**
211
+ * Clear all cached models
212
+ */
213
+ async clear() {
214
+ const db = await this.openDB();
215
+ const stores = [STORE_META, STORE_CHUNKS, STORE_STATE];
216
+ for (const storeName of stores) {
217
+ await new Promise((resolve, reject) => {
218
+ const tx = db.transaction(storeName, 'readwrite');
219
+ const store = tx.objectStore(storeName);
220
+ store.clear();
221
+ tx.oncomplete = () => resolve();
222
+ tx.onerror = () => reject(tx.error);
223
+ });
224
+ }
225
+ }
226
+ /**
227
+ * Get cache statistics
228
+ */
229
+ async getStats() {
230
+ const db = await this.openDB();
231
+ return new Promise((resolve, reject) => {
232
+ const tx = db.transaction(STORE_META, 'readonly');
233
+ const store = tx.objectStore(STORE_META);
234
+ const request = store.getAll();
235
+ request.onsuccess = () => {
236
+ const metas = request.result;
237
+ resolve({
238
+ models: metas.filter(m => m.complete).length,
239
+ totalSize: metas.reduce((sum, m) => sum + (m.complete ? m.size : 0), 0),
240
+ });
241
+ };
242
+ request.onerror = () => reject(request.error);
243
+ });
244
+ }
245
+ }
246
+ // Global cache instance
247
+ const modelCache = new ModelCache();
248
+ // ============================================================================
249
+ // Advanced Model Loader
250
+ // ============================================================================
251
+ /**
252
+ * Check if server supports Range requests
253
+ */
254
+ async function supportsRangeRequests(url) {
255
+ try {
256
+ const response = await fetch(url, { method: 'HEAD' });
257
+ const acceptRanges = response.headers.get('Accept-Ranges');
258
+ const contentLength = response.headers.get('Content-Length');
259
+ const etag = response.headers.get('ETag') ?? undefined;
260
+ return {
261
+ supports: acceptRanges === 'bytes',
262
+ size: contentLength ? parseInt(contentLength, 10) : 0,
263
+ etag,
264
+ };
265
+ }
266
+ catch {
267
+ return { supports: false, size: 0 };
268
+ }
269
+ }
270
+ /**
271
+ * Download a single chunk using Range request
272
+ */
273
+ async function downloadChunk(url, start, end, timeout) {
274
+ const controller = new AbortController();
275
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
276
+ try {
277
+ const response = await fetch(url, {
278
+ headers: { Range: `bytes=${start}-${end}` },
279
+ signal: controller.signal,
280
+ });
281
+ if (response.status !== 206 && response.status !== 200) {
282
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
283
+ }
284
+ return await response.arrayBuffer();
285
+ }
286
+ finally {
287
+ clearTimeout(timeoutId);
288
+ }
289
+ }
290
+ /**
291
+ * Download model with sharding and resume support
292
+ */
293
+ async function downloadWithResume(url, options) {
294
+ const { chunkSize = 5 * 1024 * 1024, // 5MB
295
+ parallelConnections = 4, timeout = 30000, onProgress, } = options;
296
+ // Check server capabilities
297
+ const { supports: supportsRange, size: totalSize, etag } = await supportsRangeRequests(url);
298
+ // If no Range support or small file, download normally
299
+ if (!supportsRange || totalSize < chunkSize * 2) {
300
+ return downloadSimple(url, timeout, onProgress);
301
+ }
302
+ // Check for existing download state
303
+ let state = await modelCache.getDownloadState(url);
304
+ // Initialize or reset state if needed
305
+ if (!state || (etag && state.totalSize !== totalSize)) {
306
+ const numChunks = Math.ceil(totalSize / chunkSize);
307
+ const chunks = [];
308
+ for (let i = 0; i < numChunks; i++) {
309
+ const start = i * chunkSize;
310
+ const end = Math.min(start + chunkSize - 1, totalSize - 1);
311
+ chunks.push({ index: i, start, end, downloaded: false });
312
+ }
313
+ state = {
314
+ url,
315
+ totalSize,
316
+ downloadedSize: 0,
317
+ chunks,
318
+ startedAt: Date.now(),
319
+ };
320
+ // Clear any existing chunks
321
+ await modelCache.deleteModel(url);
322
+ }
323
+ // Download remaining chunks
324
+ const pendingChunks = state.chunks.filter(c => !c.downloaded);
325
+ let downloadedSize = state.downloadedSize;
326
+ const startTime = Date.now();
327
+ let lastProgressTime = startTime;
328
+ let lastDownloadedSize = downloadedSize;
329
+ // Progress tracking
330
+ const reportProgress = () => {
331
+ if (!onProgress)
332
+ return;
333
+ const now = Date.now();
334
+ const elapsed = (now - lastProgressTime) / 1000;
335
+ const bytesDownloaded = downloadedSize - lastDownloadedSize;
336
+ const speed = elapsed > 0 ? bytesDownloaded / elapsed : 0;
337
+ const remaining = totalSize - downloadedSize;
338
+ const eta = speed > 0 ? (remaining / speed) * 1000 : 0;
339
+ onProgress({
340
+ loaded: downloadedSize,
341
+ total: totalSize,
342
+ percent: (downloadedSize / totalSize) * 100,
343
+ speed,
344
+ eta,
345
+ currentChunk: state.chunks.filter(c => c.downloaded).length,
346
+ totalChunks: state.chunks.length,
347
+ });
348
+ lastProgressTime = now;
349
+ lastDownloadedSize = downloadedSize;
350
+ };
351
+ // Download chunks in parallel
352
+ const downloadQueue = [...pendingChunks];
353
+ const inProgress = new Map();
354
+ while (downloadQueue.length > 0 || inProgress.size > 0) {
355
+ // Start new downloads up to parallelConnections limit
356
+ while (downloadQueue.length > 0 && inProgress.size < parallelConnections) {
357
+ const chunk = downloadQueue.shift();
358
+ const downloadPromise = (async () => {
359
+ try {
360
+ const data = await downloadChunk(url, chunk.start, chunk.end, timeout);
361
+ await modelCache.saveChunk(url, chunk.index, data);
362
+ chunk.downloaded = true;
363
+ downloadedSize += data.byteLength;
364
+ // Update state periodically
365
+ state.downloadedSize = downloadedSize;
366
+ await modelCache.saveDownloadState(state);
367
+ reportProgress();
368
+ }
369
+ finally {
370
+ inProgress.delete(chunk.index);
371
+ }
372
+ })();
373
+ inProgress.set(chunk.index, downloadPromise);
374
+ }
375
+ // Wait for at least one to complete
376
+ if (inProgress.size > 0) {
377
+ await Promise.race(inProgress.values());
378
+ }
379
+ }
380
+ // All chunks downloaded, merge them
381
+ const chunks = await modelCache.getChunks(url);
382
+ const result = new Uint8Array(totalSize);
383
+ let offset = 0;
384
+ for (const chunk of chunks) {
385
+ result.set(new Uint8Array(chunk), offset);
386
+ offset += chunk.byteLength;
387
+ }
388
+ // Save metadata and cleanup state
389
+ await modelCache.saveMeta({
390
+ url,
391
+ size: totalSize,
392
+ etag,
393
+ cachedAt: Date.now(),
394
+ chunks: chunks.length,
395
+ complete: true,
396
+ });
397
+ await modelCache.deleteDownloadState(url);
398
+ return result.buffer;
399
+ }
400
+ /**
401
+ * Simple download without sharding
402
+ */
403
+ async function downloadSimple(url, timeout, onProgress) {
404
+ const controller = new AbortController();
405
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
406
+ try {
407
+ const response = await fetch(url, { signal: controller.signal });
408
+ if (!response.ok) {
409
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
410
+ }
411
+ const contentLength = response.headers.get('Content-Length');
412
+ const total = contentLength ? parseInt(contentLength, 10) : 0;
413
+ if (!response.body || !onProgress || total === 0) {
414
+ return await response.arrayBuffer();
415
+ }
416
+ // Stream with progress
417
+ const reader = response.body.getReader();
418
+ const chunks = [];
419
+ let loaded = 0;
420
+ const startTime = Date.now();
421
+ while (true) {
422
+ const { done, value } = await reader.read();
423
+ if (done)
424
+ break;
425
+ chunks.push(value);
426
+ loaded += value.length;
427
+ const elapsed = (Date.now() - startTime) / 1000;
428
+ const speed = elapsed > 0 ? loaded / elapsed : 0;
429
+ const remaining = total - loaded;
430
+ const eta = speed > 0 ? (remaining / speed) * 1000 : 0;
431
+ onProgress({
432
+ loaded,
433
+ total,
434
+ percent: (loaded / total) * 100,
435
+ speed,
436
+ eta,
437
+ });
438
+ }
439
+ // Merge chunks
440
+ const result = new Uint8Array(loaded);
441
+ let offset = 0;
442
+ for (const chunk of chunks) {
443
+ result.set(chunk, offset);
444
+ offset += chunk.length;
445
+ }
446
+ return result.buffer;
447
+ }
448
+ finally {
449
+ clearTimeout(timeoutId);
450
+ }
451
+ }
452
+ /**
453
+ * Preload manager for background model loading
454
+ */
455
+ class PreloadManager {
456
+ tasks = new Map();
457
+ queue = [];
458
+ maxConcurrent = 2;
459
+ activeCount = 0;
460
+ /**
461
+ * Preload a model in the background
462
+ */
463
+ preload(url, options = {}) {
464
+ // Check if already preloading
465
+ const existing = this.tasks.get(url);
466
+ if (existing) {
467
+ return existing.promise;
468
+ }
469
+ // Create task
470
+ let resolve;
471
+ let reject;
472
+ const promise = new Promise((res, rej) => {
473
+ resolve = res;
474
+ reject = rej;
475
+ });
476
+ const task = {
477
+ url,
478
+ priority: options.priority ?? 0,
479
+ options,
480
+ promise,
481
+ resolve,
482
+ reject,
483
+ status: 'pending',
484
+ };
485
+ this.tasks.set(url, task);
486
+ // Insert into queue based on priority
487
+ const insertIndex = this.queue.findIndex(u => {
488
+ const t = this.tasks.get(u);
489
+ return t && t.priority < task.priority;
490
+ });
491
+ if (insertIndex === -1) {
492
+ this.queue.push(url);
493
+ }
494
+ else {
495
+ this.queue.splice(insertIndex, 0, url);
496
+ }
497
+ // Process queue
498
+ this.processQueue();
499
+ return promise;
500
+ }
501
+ /**
502
+ * Process the preload queue
503
+ */
504
+ async processQueue() {
505
+ while (this.queue.length > 0 && this.activeCount < this.maxConcurrent) {
506
+ const url = this.queue.shift();
507
+ if (!url)
508
+ break;
509
+ const task = this.tasks.get(url);
510
+ if (!task || task.status !== 'pending')
511
+ continue;
512
+ this.activeCount++;
513
+ task.status = 'loading';
514
+ this.downloadTask(task).finally(() => {
515
+ this.activeCount--;
516
+ this.processQueue();
517
+ });
518
+ }
519
+ }
520
+ /**
521
+ * Download a preload task
522
+ */
523
+ async downloadTask(task) {
524
+ try {
525
+ const data = await loadModelData(task.url, task.options);
526
+ task.status = 'complete';
527
+ task.resolve(data);
528
+ }
529
+ catch (error) {
530
+ task.status = 'error';
531
+ task.reject(error instanceof Error ? error : new Error(String(error)));
532
+ }
533
+ }
534
+ /**
535
+ * Check if a model is preloaded
536
+ */
537
+ isPreloaded(url) {
538
+ const task = this.tasks.get(url);
539
+ return task?.status === 'complete';
540
+ }
541
+ /**
542
+ * Get preload status
543
+ */
544
+ getStatus(url) {
545
+ const task = this.tasks.get(url);
546
+ return task?.status ?? 'not_found';
547
+ }
548
+ /**
549
+ * Get preloaded model data
550
+ */
551
+ async get(url) {
552
+ const task = this.tasks.get(url);
553
+ if (!task)
554
+ return null;
555
+ if (task.status === 'complete' || task.status === 'loading') {
556
+ return task.promise;
557
+ }
558
+ return null;
559
+ }
560
+ /**
561
+ * Cancel preload
562
+ */
563
+ cancel(url) {
564
+ const task = this.tasks.get(url);
565
+ if (task && task.status === 'pending') {
566
+ this.tasks.delete(url);
567
+ this.queue = this.queue.filter(u => u !== url);
568
+ task.reject(new Error('Preload cancelled'));
569
+ }
570
+ }
571
+ /**
572
+ * Clear all preloads
573
+ */
574
+ clear() {
575
+ for (const [, task] of this.tasks) {
576
+ if (task.status === 'pending') {
577
+ task.reject(new Error('Preload cleared'));
578
+ }
579
+ }
580
+ this.tasks.clear();
581
+ this.queue = [];
582
+ }
583
+ }
584
+ // Global preload manager
585
+ const preloadManager = new PreloadManager();
586
+ // ============================================================================
587
+ // Public API
588
+ // ============================================================================
589
+ /**
590
+ * Load model data with caching, sharding, and resume support
591
+ */
592
+ export async function loadModelData(url, options = {}) {
593
+ const { cache = true, forceDownload = false, resumable = true, } = options;
594
+ // Check cache first
595
+ if (cache && !forceDownload) {
596
+ const cached = await modelCache.getModel(url);
597
+ if (cached) {
598
+ console.log(`✓ Model loaded from cache: ${url}`);
599
+ options.onProgress?.({
600
+ loaded: cached.byteLength,
601
+ total: cached.byteLength,
602
+ percent: 100,
603
+ speed: 0,
604
+ eta: 0,
605
+ });
606
+ return cached;
607
+ }
608
+ }
609
+ // Download with resume support
610
+ let data;
611
+ if (resumable) {
612
+ data = await downloadWithResume(url, options);
613
+ }
614
+ else {
615
+ data = await downloadSimple(url, options.timeout ?? 30000, options.onProgress);
616
+ }
617
+ // Cache the result
618
+ if (cache) {
619
+ // For simple downloads, save as single chunk
620
+ if (!resumable) {
621
+ await modelCache.saveChunk(url, 0, data);
622
+ await modelCache.saveMeta({
623
+ url,
624
+ size: data.byteLength,
625
+ cachedAt: Date.now(),
626
+ chunks: 1,
627
+ complete: true,
628
+ });
629
+ }
630
+ }
631
+ return data;
632
+ }
633
+ /**
634
+ * Preload a model in the background
635
+ */
636
+ export function preloadModel(url, options = {}) {
637
+ return preloadManager.preload(url, options);
638
+ }
639
+ /**
640
+ * Preload multiple models
641
+ */
642
+ export function preloadModels(urls, options = {}) {
643
+ return Promise.all(urls.map(({ url, priority }) => preloadManager.preload(url, { ...options, priority })));
644
+ }
645
+ /**
646
+ * Check if a model is cached
647
+ */
648
+ export async function isModelCached(url) {
649
+ const meta = await modelCache.getMeta(url);
650
+ return meta?.complete ?? false;
651
+ }
652
+ /**
653
+ * Get cached model data
654
+ */
655
+ export async function getCachedModel(url) {
656
+ return modelCache.getModel(url);
657
+ }
658
+ /**
659
+ * Delete a cached model
660
+ */
661
+ export async function deleteCachedModel(url) {
662
+ return modelCache.deleteModel(url);
663
+ }
664
+ /**
665
+ * Clear all cached models
666
+ */
667
+ export async function clearModelCache() {
668
+ return modelCache.clear();
669
+ }
670
+ /**
671
+ * Get model cache statistics
672
+ */
673
+ export async function getModelCacheStats() {
674
+ return modelCache.getStats();
675
+ }
676
+ /**
677
+ * Get preload status
678
+ */
679
+ export function getPreloadStatus(url) {
680
+ return preloadManager.getStatus(url);
681
+ }
682
+ /**
683
+ * Cancel a preload
684
+ */
685
+ export function cancelPreload(url) {
686
+ preloadManager.cancel(url);
687
+ }
688
+ /**
689
+ * Get preloaded model (or wait for preload to complete)
690
+ */
691
+ export async function getPreloadedModel(url) {
692
+ return preloadManager.get(url);
693
+ }
694
+ //# sourceMappingURL=model-loader.js.map