/** * ModelLoader - Bộ Tải Mô Hình * Tải danh sách mô hình từ models/models.json và các tệp mô hình ONNX * Requirements: 1.1, 1.2, 1.3, 1.5, 3.1, 3.2 */ class ModelLoader { constructor() { /** @type {Array|null} In-memory cache for the model list */ this._modelListCache = null; } /** * Tải danh sách mô hình từ tệp cấu hình JSON * @param {string} configPath - Đường dẫn đến tệp models.json * @returns {Promise>} */ async loadModelList(configPath = CONFIG.MODELS_CONFIG_FILE) { // Return cached list if available (Requirement 15.4) if (this._modelListCache !== null) { console.log('[ModelLoader] Returning cached model list'); return this._modelListCache; } try { const response = await fetch(configPath); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const json = await response.json(); if (!json || !Array.isArray(json.models)) { throw new Error('Invalid models.json format: missing "models" array'); } const models = json.models.map(model => ({ id: model.id || '', name: model.name || '', description: model.description || '', path: model.path || '', category: model.category || '', version: model.version || '', size: model.size || 0, labelsFile: model.labelsFile || null })); // Cache the result in memory this._modelListCache = models; return models; } catch (error) { console.error('[ModelLoader] Failed to load model list:', error); return []; } } /** * Tải tệp mô hình ONNX từ đường dẫn * @param {string} filePath - Đường dẫn đến tệp .onnx * @returns {Promise} * @throws {Error} Nếu không thể tải tệp */ async loadModelFile(filePath) { const response = await fetch(filePath); if (!response.ok) { throw new Error(`Failed to load model file "${filePath}": HTTP ${response.status} ${response.statusText}`); } const buffer = await response.arrayBuffer(); return buffer; } /** * Xử lý tải lên tệp từ người dùng * @param {File} file - Đối tượng File từ input hoặc drag-and-drop * @returns {Promise<{success: boolean, data?: ArrayBuffer, error?: string}>} */ async handleFileUpload(file) { // Xác thực tệp tồn tại if (!file) { return { success: false, error: 'No file provided.' }; } // Xác thực phần mở rộng tệp const fileName = file.name || ''; const hasValidExtension = CONFIG.FILE.ALLOWED_EXTENSIONS.some(ext => fileName.toLowerCase().endsWith(ext) ); if (!hasValidExtension) { const allowed = CONFIG.FILE.ALLOWED_EXTENSIONS.join(', '); return { success: false, error: `Invalid file type. Only ${allowed} files are supported.` }; } // Xác thực kích thước tệp if (file.size > CONFIG.FILE.MAX_FILE_SIZE) { const maxMB = Math.round(CONFIG.FILE.MAX_FILE_SIZE / (1024 * 1024)); return { success: false, error: `File is too large. Maximum allowed size is ${maxMB}MB.` }; } // Đọc tệp dưới dạng ArrayBuffer try { const data = await this._readFileAsArrayBuffer(file); return { success: true, data }; } catch (error) { console.error('[ModelLoader] Failed to read uploaded file:', error); return { success: false, error: `Failed to read file: ${error.message || CONFIG.ERRORS.UPLOAD_ERROR}` }; } } /** * Đọc File object dưới dạng ArrayBuffer * @param {File} file * @returns {Promise} * @private */ _readFileAsArrayBuffer(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = (event) => resolve(event.target.result); reader.onerror = () => reject(new Error(reader.error ? reader.error.message : 'FileReader error')); reader.readAsArrayBuffer(file); }); } } // Export as global for browser usage window.ModelLoader = ModelLoader;