Spaces:
Running
Running
| /** | |
| * 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; | |