Spaces:
Running
Running
| /** | |
| * ExportHandler - Handles exporting model information as JSON | |
| * Requirements: 12.1, 12.2, 12.3, 12.4 | |
| */ | |
| const ExportHandler = (function () { | |
| class ExportHandler { | |
| /** | |
| * @param {string} [exportBtnId='exportBtn'] - ID of the export button element | |
| */ | |
| constructor(exportBtnId = 'exportBtn') { | |
| this._exportBtn = document.getElementById(exportBtnId); | |
| this._unsubscribe = null; | |
| this._init(); | |
| } | |
| // βββ Private ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _init() { | |
| if (!this._exportBtn) { | |
| console.warn('[ExportHandler] Export button not found:', this._exportBtn); | |
| return; | |
| } | |
| // Bind click handler | |
| this._exportBtn.addEventListener('click', () => this._handleExport()); | |
| // Subscribe to currentModel changes to enable/disable button | |
| this._unsubscribe = StateManager.subscribe('currentModel', (model) => { | |
| this._setButtonEnabled(!!model); | |
| }); | |
| // Set initial state | |
| this._setButtonEnabled(!!StateManager.getCurrentModel()); | |
| } | |
| /** | |
| * Enable or disable the export button. | |
| * @param {boolean} enabled | |
| */ | |
| _setButtonEnabled(enabled) { | |
| if (!this._exportBtn) return; | |
| this._exportBtn.disabled = !enabled; | |
| } | |
| /** | |
| * Build the export payload from the current model. | |
| * @param {object} model - ParsedModel from StateManager | |
| * @returns {object} | |
| */ | |
| _buildExportData(model) { | |
| const data = {}; | |
| // Metadata | |
| data.metadata = model.metadata ? Object.assign({}, model.metadata) : {}; | |
| // Inputs | |
| data.inputs = Array.isArray(model.inputs) | |
| ? model.inputs.map((i) => Object.assign({}, i)) | |
| : []; | |
| // Outputs | |
| data.outputs = Array.isArray(model.outputs) | |
| ? model.outputs.map((o) => Object.assign({}, o)) | |
| : []; | |
| // Graph (nodes + edges) | |
| if (model.graph) { | |
| data.graph = { | |
| name: model.graph.name || '', | |
| nodes: Array.isArray(model.graph.nodes) | |
| ? model.graph.nodes.map((n) => Object.assign({}, n)) | |
| : [], | |
| edges: Array.isArray(model.graph.edges) | |
| ? model.graph.edges.map((e) => Object.assign({}, e)) | |
| : [], | |
| }; | |
| } else { | |
| data.graph = { name: '', nodes: [], edges: [] }; | |
| } | |
| // Initializers | |
| data.initializers = Array.isArray(model.initializers) | |
| ? model.initializers.map((init) => Object.assign({}, init)) | |
| : []; | |
| return data; | |
| } | |
| /** | |
| * Derive the download file name from the model's fileName metadata. | |
| * e.g. "model.onnx" β "model_info.json" | |
| * @param {object} metadata | |
| * @returns {string} | |
| */ | |
| _buildFileName(metadata) { | |
| const fileName = (metadata && metadata.fileName) || 'model'; | |
| // Strip directory path, keep base name | |
| const base = fileName.split('/').pop().split('\\').pop(); | |
| // Remove extension | |
| const withoutExt = base.replace(/\.[^.]+$/, ''); | |
| return `${withoutExt}_info.json`; | |
| } | |
| /** | |
| * Trigger a browser download of the given JSON string. | |
| * @param {string} jsonStr | |
| * @param {string} fileName | |
| */ | |
| _triggerDownload(jsonStr, fileName) { | |
| const blob = new Blob([jsonStr], { type: 'application/json' }); | |
| const url = URL.createObjectURL(blob); | |
| const anchor = document.createElement('a'); | |
| anchor.href = url; | |
| anchor.download = fileName; | |
| anchor.style.display = 'none'; | |
| document.body.appendChild(anchor); | |
| anchor.click(); | |
| document.body.removeChild(anchor); | |
| // Release the object URL after a short delay | |
| setTimeout(() => URL.revokeObjectURL(url), 1000); | |
| } | |
| /** | |
| * Show a brief success or error notification. | |
| * Falls back to console if no ErrorDisplay is available. | |
| * @param {string} message | |
| * @param {'success'|'error'} type | |
| */ | |
| _showNotification(message, type) { | |
| if (window.ErrorDisplay && typeof window.ErrorDisplay.show === 'function') { | |
| window.ErrorDisplay.show(message, type === 'error' ? 'error' : 'info'); | |
| } else { | |
| if (type === 'error') { | |
| console.error('[ExportHandler]', message); | |
| } else { | |
| console.info('[ExportHandler]', message); | |
| } | |
| } | |
| } | |
| // βββ Export Handler ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _handleExport() { | |
| try { | |
| const model = StateManager.getCurrentModel(); | |
| if (!model) { | |
| this._showNotification( | |
| CONFIG.ERRORS.EXPORT_ERROR + ' No model is currently loaded.', | |
| 'error' | |
| ); | |
| return; | |
| } | |
| const exportData = this._buildExportData(model); | |
| const jsonStr = JSON.stringify(exportData, null, 2); | |
| const fileName = this._buildFileName(model.metadata); | |
| this._triggerDownload(jsonStr, fileName); | |
| this._showNotification(CONFIG.SUCCESS.MODEL_EXPORTED, 'success'); | |
| } catch (err) { | |
| console.error('[ExportHandler] Export failed:', err); | |
| this._showNotification(CONFIG.ERRORS.EXPORT_ERROR, 'error'); | |
| } | |
| } | |
| // βββ Public API ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| /** | |
| * Programmatically trigger an export (useful for testing). | |
| */ | |
| export() { | |
| this._handleExport(); | |
| } | |
| /** | |
| * Clean up subscriptions. | |
| */ | |
| destroy() { | |
| if (typeof this._unsubscribe === 'function') { | |
| this._unsubscribe(); | |
| this._unsubscribe = null; | |
| } | |
| } | |
| } | |
| return ExportHandler; | |
| })(); | |
| // Export for global access in vanilla JS context | |
| window.ExportHandler = ExportHandler; | |