model-explorer / js /ui /opsetChecker.js
mr4's picture
Upload 71 files
9bd422a verified
/**
* OpsetChecker - Displays opset compatibility information for a loaded ONNX model
* Shows current opset version, compares against standard versions, lists domains and custom operators.
* Requirements: 22.1, 22.2, 22.3, 22.4, 22.5
*/
class OpsetChecker {
/**
* Standard ONNX opset versions to compare against.
* @type {number[]}
*/
static STANDARD_OPSETS = [7, 9, 11, 13, 15, 17, 18, 19, 20, 21];
/**
* @param {string} containerId - ID of the container element
*/
constructor(containerId) {
this._containerId = containerId;
this._container = document.getElementById(containerId);
this._data = null;
if (!this._container) {
console.warn(`[OpsetChecker] Container #${containerId} not found`);
}
this._setupEventListeners();
}
// ─── Private ──────────────────────────────────────────────────────────────
/**
* Listen for model:loaded events to auto-update.
*/
_setupEventListeners() {
if (window.EventBus && typeof CONFIG !== 'undefined' && CONFIG.EVENTS) {
window.EventBus.on(CONFIG.EVENTS.MODEL_LOADED, (data) => {
if (data && data.model) {
const result = this.compute(data.model);
this.render(result);
}
});
}
}
/**
* Escape HTML special characters.
* @param {string} str
* @returns {string}
*/
_escapeHtml(str) {
const div = document.createElement('div');
div.appendChild(document.createTextNode(String(str)));
return div.innerHTML;
}
// ─── Public API ───────────────────────────────────────────────────────────
/**
* Compute opset compatibility data from a parsed model.
* @param {object} parsedModel
* @returns {{ modelOpset: number, compatibility: Array<{version: number, compatible: boolean}>, domains: Array<{domain: string, version: number}>, customOperators: Array<{name: string, domain: string}>, allStandard: boolean }}
*/
compute(parsedModel) {
const modelOpset = (parsedModel && parsedModel.metadata && parsedModel.metadata.opsetVersion) || 0;
// Compare against standard opset versions
// Compatible means the standard version >= model's opset (runtime can run the model)
const compatibility = OpsetChecker.STANDARD_OPSETS.map((version) => ({
version,
compatible: version >= modelOpset,
}));
// Extract all domains and versions from opset_import
const opsetImport = (parsedModel && parsedModel.metadata && Array.isArray(parsedModel.metadata.opsetImport))
? parsedModel.metadata.opsetImport
: [];
const domains = opsetImport.map((op) => ({
domain: op.domain || 'ai.onnx (default)',
version: op.version || 0,
}));
// Find custom domain operators from graph nodes
const nodes = (parsedModel && parsedModel.graph && parsedModel.graph.nodes) || [];
const customOperators = [];
const seen = new Set();
for (const node of nodes) {
if (node.domain && node.domain !== '' && node.domain !== 'ai.onnx') {
const key = `${node.domain}::${node.opType}`;
if (!seen.has(key)) {
seen.add(key);
customOperators.push({ name: node.opType || 'Unknown', domain: node.domain });
}
}
}
const allStandard = customOperators.length === 0;
this._data = { modelOpset, compatibility, domains, customOperators, allStandard };
return this._data;
}
/**
* Render the opset compatibility information into the container.
* @param {{ modelOpset: number, compatibility: Array, domains: Array, customOperators: Array, allStandard: boolean }} data
*/
render(data) {
if (!this._container) return;
if (!data) {
this._container.innerHTML = '<p class="text-muted">No opset information available.</p>';
return;
}
const { modelOpset, compatibility, domains, customOperators, allStandard } = data;
let html = '';
// Current opset version
html += `
<div class="mb-3">
<div class="small">
<i class="fas fa-tag me-1"></i>
<strong>Model Opset Version:</strong> ${modelOpset}
</div>
</div>`;
// Standard opset compatibility table
html += `
<div class="mb-3">
<div class="small fw-bold mb-1">Standard Opset Compatibility</div>
<div class="d-flex flex-wrap gap-1">`;
for (const entry of compatibility) {
const icon = entry.compatible ? 'fa-check-circle text-success' : 'fa-times-circle text-danger';
const label = entry.compatible ? 'Compatible' : 'Incompatible';
html += `
<span class="badge ${entry.compatible ? 'bg-success' : 'bg-danger'} bg-opacity-10 text-${entry.compatible ? 'success' : 'danger'} border"
title="Opset ${entry.version}: ${label}"
aria-label="Opset ${entry.version}: ${label}">
<i class="fas ${icon} me-1"></i>${entry.version}
</span>`;
}
html += `
</div>
</div>`;
// Domains from opset_import
if (domains.length > 0) {
html += `
<div class="mb-3">
<div class="small fw-bold mb-1">Opset Imports</div>
<table class="table table-sm table-hover mb-0" role="grid" aria-label="Opset imports">
<thead>
<tr>
<th scope="col">Domain</th>
<th scope="col" class="text-end">Version</th>
</tr>
</thead>
<tbody>`;
for (const d of domains) {
html += `
<tr>
<td>${this._escapeHtml(d.domain)}</td>
<td class="text-end">${d.version}</td>
</tr>`;
}
html += `
</tbody>
</table>
</div>`;
}
// Custom domain operators
if (allStandard) {
html += `
<div class="small text-success">
<i class="fas fa-check me-1"></i>All operators compatible with standard ONNX
</div>`;
} else {
html += `
<div class="mb-2">
<div class="small fw-bold mb-1">Custom Domain Operators</div>
<table class="table table-sm table-hover mb-0" role="grid" aria-label="Custom domain operators">
<thead>
<tr>
<th scope="col">Operator</th>
<th scope="col">Domain</th>
</tr>
</thead>
<tbody>`;
for (const op of customOperators) {
html += `
<tr>
<td>${this._escapeHtml(op.name)}</td>
<td>${this._escapeHtml(op.domain)}</td>
</tr>`;
}
html += `
</tbody>
</table>
</div>`;
}
this._container.innerHTML = html;
}
/**
* Clear the display.
*/
clear() {
this._data = null;
if (!this._container) return;
this._container.innerHTML = '<p class="text-muted">Select a model to view opset compatibility</p>';
}
/**
* Get the last computed data.
* @returns {object|null}
*/
getData() {
return this._data;
}
}
window.OpsetChecker = OpsetChecker;