File size: 4,829 Bytes
9bd422a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/**
 * 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<Array<{id, name, description, path, category, version, size, labelsFile?}>>}
     */
    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<ArrayBuffer>}
     * @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<ArrayBuffer>}
     * @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;