model-explorer / js /ui /inputOutputDisplay.js
mr4's picture
Upload 71 files
9bd422a verified
/**
* InputOutputDisplay - Displays model inputs and outputs
* Requirements: 5.1, 5.2, 5.3, 5.4, 5.5, 5.6
*/
class InputOutputDisplay {
/**
* @param {string} containerId - ID of the container element
*/
constructor(containerId) {
this._containerId = containerId;
this._container = document.getElementById(containerId);
if (!this._container) {
console.warn(`[InputOutputDisplay] Container #${containerId} not found`);
}
}
// ─── Private ──────────────────────────────────────────────────────────────
/**
* 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;
}
/**
* Format a tensor shape array to a human-readable string.
* Dynamic dimensions (negative numbers or strings) are shown as-is.
* e.g. [1, 3, 224, 224] β†’ "[1, 3, 224, 224]"
* [-1, 3, 224, 224] β†’ "[-1, 3, 224, 224]"
* ["batch", 3, 224, 224] β†’ "[batch, 3, 224, 224]"
* @param {Array<number|string>} shape
* @returns {string}
*/
_formatShape(shape) {
if (!shape || shape.length === 0) return '[]';
const dims = shape.map((d) => {
if (typeof d === 'string') return d;
if (d === -1 || d < 0) return 'dynamic';
return String(d);
});
return `[${dims.join(', ')}]`;
}
/**
* Resolve a numeric data type to its string name.
* @param {number|string} dataType
* @returns {string}
*/
_resolveDataType(dataType) {
if (typeof dataType === 'string') return dataType;
if (CONFIG && CONFIG.DATA_TYPES && CONFIG.DATA_TYPES[dataType]) {
return CONFIG.DATA_TYPES[dataType];
}
return String(dataType);
}
/**
* Build the HTML for a single tensor entry (input or output).
* @param {TensorInfo} tensor
* @param {'input'|'output'} kind
* @returns {string}
*/
_buildTensorItem(tensor, kind) {
const badgeClass = kind === 'input' ? 'bg-primary' : 'bg-success';
const badgeLabel = kind === 'input' ? 'Input' : 'Output';
const shape = this._formatShape(tensor.shape);
const dtype = this._resolveDataType(tensor.dataType);
return `
<div class="tensor-item border rounded p-2 mb-2 cursor-pointer"
role="button"
tabindex="0"
data-tensor-name="${this._escapeHtml(tensor.name)}"
data-kind="${kind}"
title="Click to highlight in graph"
aria-label="${this._escapeHtml(tensor.name)} ${badgeLabel}">
<div class="d-flex align-items-center justify-content-between mb-1">
<span class="fw-medium text-truncate me-2">${this._escapeHtml(tensor.name)}</span>
<span class="badge ${badgeClass} flex-shrink-0">${badgeLabel}</span>
</div>
<div class="small text-muted">
<span class="me-3"><i class="fas fa-shapes me-1"></i>${this._escapeHtml(shape)}</span>
<span><i class="fas fa-tag me-1"></i>${this._escapeHtml(dtype)}</span>
</div>
${tensor.description ? `<div class="small text-secondary mt-1 fst-italic">${this._escapeHtml(tensor.description)}</div>` : ''}
</div>`;
}
/**
* Attach click (and keyboard) handlers to tensor items.
*/
_attachItemHandlers() {
if (!this._container) return;
const items = this._container.querySelectorAll('.tensor-item');
items.forEach((item) => {
const handler = () => {
const name = item.dataset.tensorName;
if (name && window.EventBus) {
window.EventBus.emit(CONFIG.EVENTS.NODE_SELECTED, { nodeId: name, source: 'inputOutput' });
}
if (name && window.StateManager) {
window.StateManager.setSelectedNodeId(name);
}
};
item.addEventListener('click', handler);
item.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handler();
}
});
});
}
// ─── Public API ───────────────────────────────────────────────────────────
/**
* Render inputs and outputs for a parsed model.
* @param {ParsedModel} model
*/
render(model) {
if (!this._container) return;
const inputs = (model && model.inputs) ? model.inputs : [];
const outputs = (model && model.outputs) ? model.outputs : [];
if (inputs.length === 0 && outputs.length === 0) {
this._container.innerHTML = '<p class="text-muted">No inputs or outputs found.</p>';
return;
}
let html = '';
if (inputs.length > 0) {
html += `<h6 class="text-primary mb-2"><i class="fas fa-arrow-right me-1"></i>Inputs (${inputs.length})</h6>`;
html += inputs.map((t) => this._buildTensorItem(t, 'input')).join('');
}
if (outputs.length > 0) {
html += `<h6 class="text-success mb-2 ${inputs.length > 0 ? 'mt-3' : ''}"><i class="fas fa-arrow-left me-1"></i>Outputs (${outputs.length})</h6>`;
html += outputs.map((t) => this._buildTensorItem(t, 'output')).join('');
}
this._container.innerHTML = html;
this._attachItemHandlers();
}
/**
* Clear the display.
*/
clear() {
if (!this._container) return;
this._container.innerHTML = '<p class="text-muted">Select a model to view inputs and outputs</p>';
}
}
window.InputOutputDisplay = InputOutputDisplay;