diff --git a/source/index.html b/source/index.html index a9ecc67f6d..69a598dc57 100644 --- a/source/index.html +++ b/source/index.html @@ -246,7 +246,10 @@ } .sidebar-separator { margin-bottom: 20px; } .sidebar-find-search { display: flex; align-items: center; background: #fff; border-radius: 16px; margin: 0px 20px 8px 20px; padding: 0px 8px 0px 8px; } -.sidebar-find-query { width: 100vw; background: none; font-family: inherit; font-size: 13px; padding: 8px 8px 8px 8px; border: 0; outline: 0; } +.sidebar-find-query { flex: 1; background: none; font-family: inherit; font-size: 13px; padding: 8px 8px 8px 8px; border: 0; outline: 0; } +.sidebar-find-nodetype-dropdown { position: absolute; right: 0; top: 100%; margin-top: 4px; background: #fff; border: 1px solid #ddd; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.15); z-index: 1000; min-width: 120px; max-height: 200px; overflow-y: auto; } +.sidebar-find-nodetype-dropdown > div { padding: 6px 12px; cursor: pointer; font-size: 12px; color: #333; } +.sidebar-find-nodetype-dropdown > div:hover { background: #f5f5f5; } .sidebar-find-toggle { margin-left: auto; margin-right: 2px; width: 16px; height: 16px; cursor: pointer; } .sidebar-find-toggle input[type="checkbox"] { display: none; } .sidebar-find-toggle input[type="checkbox"]:not(:checked) + svg { fill: #ccc; stroke: #ccc; } @@ -290,6 +293,9 @@ .sidebar-documentation code { background-color: #1e1e1e; } .sidebar-documentation pre { background-color: #1e1e1e; } .sidebar-find-search { background: #383838; color: #dfdfdf; border-color: #424242; } + .sidebar-find-nodetype-dropdown { background: #383838; border-color: #555; } + .sidebar-find-nodetype-dropdown > div { color: #dfdfdf; } + .sidebar-find-nodetype-dropdown > div:hover { background: #424242; } .sidebar-find-toggle input[type="checkbox"]:not(:checked) + svg { fill: #555; stroke: #555; } .sidebar-find-toggle input[type="checkbox"]:checked + svg { fill: #aaa; stroke: #aaa; } .sidebar-find-content li { color: #aaaaaa; } @@ -334,6 +340,11 @@

+ + + + + diff --git a/source/view.js b/source/view.js index 327e7862fb..2b1972efef 100644 --- a/source/view.js +++ b/source/view.js @@ -4072,13 +4072,15 @@ view.FindSidebar = class extends view.Control { query: '', node: true, connection: true, - weight: true + weight: true, + nodeType: '' }; this._toggles = { node: { hide: 'Hide Nodes', show: 'Show Nodes' }, connection: { hide: 'Hide Connections', show: 'Show Connections' }, weight: { hide: 'Hide Weights', show: 'Show Weights' } }; + this._isOnnxModel = this._view._model && this._view._model.format && this._view._model.format.startsWith('ONNX'); } get identifier() { @@ -4194,6 +4196,13 @@ view.FindSidebar = class extends view.Control { const name = node.name; const type = node.type.name; const identifier = node.identifier; + // Apply node type filter for ONNX models + if (this._isOnnxModel && this._state.nodeType && this._state.nodeType !== '') { + const nodeTypeName = type.split('.').pop(); // Get base type name (e.g., "Conv" from "ai.onnx.Conv") + if (nodeTypeName.toLowerCase() !== this._state.nodeType.toLowerCase()) { + return; // Skip nodes that don't match the selected type + } + } if ((name && this._term(name)) || (type && this._term(type)) || (identifier && this._term(identifier))) { const content = `${name || `[${type}]`}`; this._add(node, content, 'node'); @@ -4259,6 +4268,20 @@ view.FindSidebar = class extends view.Control { } } + _collectNodeTypes() { + if (!this._isOnnxModel || !this._target || !this._target.nodes) { + return []; + } + const nodeTypes = new Set(); + for (const node of this._target.nodes) { + if (node.type && node.type.name) { + const typeName = node.type.name.split('.').pop(); // Get base type name (e.g., "Conv" from "ai.onnx.Conv") + nodeTypes.add(typeName); + } + } + return Array.from(nodeTypes).sort(); + } + _update() { try { this._reset(); @@ -4295,6 +4318,97 @@ view.FindSidebar = class extends view.Control { this._search = this.createElement('div', 'sidebar-find-search'); this._query = this.createElement('input', 'sidebar-find-query'); this._search.appendChild(this._query); + + // Add node type filter button for ONNX models + if (this._isOnnxModel) { + const container = this.createElement('div'); + container.style.position = 'relative'; + + this._nodeTypeFilterButton = this.createElement('label', 'sidebar-find-toggle'); + this._nodeTypeFilterButton.innerHTML = ``; + this._nodeTypeFilterButton.setAttribute('title', 'Filter by Node Type'); + + // Create dropdown menu (initially hidden) + this._nodeTypeDropdown = this.createElement('div', 'sidebar-find-nodetype-dropdown'); + this._nodeTypeDropdown.style.display = 'none'; + + const defaultOption = this.createElement('div'); + defaultOption.style.padding = '6px 12px'; + defaultOption.style.cursor = 'pointer'; + defaultOption.style.fontSize = '12px'; + defaultOption.textContent = 'All Types'; + defaultOption.addEventListener('click', () => { + this._state.nodeType = ''; + this._updateDropdownSelection(); + this.emit('state-changed', this._state); + this._update(); + this._nodeTypeDropdown.style.display = 'none'; + }); + this._nodeTypeDropdown.appendChild(defaultOption); + + const nodeTypes = this._collectNodeTypes(); + for (const nodeType of nodeTypes) { + const option = this.createElement('div'); + option.style.padding = '6px 12px'; + option.style.cursor = 'pointer'; + option.style.fontSize = '12px'; + option.textContent = nodeType; + option.addEventListener('click', () => { + this._state.nodeType = nodeType; + this._updateDropdownSelection(); + this.emit('state-changed', this._state); + this._update(); + this._nodeTypeDropdown.style.display = 'none'; + }); + option.addEventListener('mouseenter', () => { + if (this._state.nodeType !== nodeType) { + option.style.background = '#f5f5f5'; + } + }); + option.addEventListener('mouseleave', () => { + if (this._state.nodeType !== nodeType) { + option.style.background = 'transparent'; + } + }); + this._nodeTypeDropdown.appendChild(option); + } + + container.appendChild(this._nodeTypeFilterButton); + container.appendChild(this._nodeTypeDropdown); + + this._nodeTypeFilterButton.addEventListener('click', (e) => { + e.stopPropagation(); + const isVisible = this._nodeTypeDropdown.style.display !== 'none'; + this._nodeTypeDropdown.style.display = isVisible ? 'none' : 'block'; + }); + + // Close dropdown when clicking outside + const closeDropdown = (e) => { + if (container && !container.contains(e.target)) { + this._nodeTypeDropdown.style.display = 'none'; + } + }; + this._host.document.addEventListener('click', closeDropdown); + + const nodeTypesList = nodeTypes; + this._updateDropdownSelection = () => { + const options = this._nodeTypeDropdown.children; + for (let i = 0; i < options.length; i++) { + const option = options[i]; + const isSelected = (i === 0 && this._state.nodeType === '') || + (i > 0 && this._state.nodeType === nodeTypesList[i - 1]); + option.style.color = isSelected ? '#2e6bd2' : ''; + option.style.fontWeight = isSelected ? 'bold' : 'normal'; + if (!isSelected) { + option.style.background = 'transparent'; + } + } + }; + + this._search.appendChild(container); + this._updateDropdownSelection(); + } + this._content = this.createElement('ol', 'sidebar-find-content'); this._elements = [this._query, this._content]; this._query.setAttribute('id', 'search'); @@ -4359,6 +4473,9 @@ view.FindSidebar = class extends view.Control { toggle.checkbox.checked = this._state[name]; toggle.element.setAttribute('title', this._state[name] ? toggle.hide : toggle.show); } + if (this._isOnnxModel && this._updateDropdownSelection) { + this._updateDropdownSelection(); + } this._update(); this._host.event('open_sidebar', { sidebar_identifier: this.identifier,