import * as base from './base.js'; import * as grapher from './grapher.js'; const view = {}; const markdown = {}; const png = {}; const metadata = {}; const metrics = {}; view.View = class { constructor(host) { this._host = host; this._defaultOptions = { weights: true, attributes: false, names: false, blocks: true, direction: 'vertical', mousewheel: 'scroll' }; this._options = { ...this._defaultOptions }; this._events = {}; this._events.selectionchange = () => this._selectionChangeHandler(); this._model = null; this._path = []; this._selection = []; this._sidebar = new view.Sidebar(this._host); this._find = null; this._modelFactoryService = new view.ModelFactoryService(this._host); this._modelFactoryService.import(); this._worker = this._host.environment('serial') ? null : new view.Worker(this._host); } async start() { try { const zip = await import('./zip.js'); await zip.Archive.import(); await this._host.view(this); const options = this._host.get('options') || {}; for (const [name, value] of Object.entries(options)) { this._options[name] = value; } this._element('sidebar-model-button').addEventListener('click', () => { this.showModelProperties(); }); this._element('sidebar-target-button').addEventListener('click', () => { this.showTargetProperties(); }); this._element('zoom-in-button').addEventListener('click', () => { this.zoomIn(); }); this._element('zoom-out-button').addEventListener('click', () => { this.zoomOut(); }); this._element('toolbar-path-back-button').addEventListener('click', async () => { await this.popTarget(); }); this._element('sidebar').addEventListener('mousewheel', (e) => { if (e.shiftKey || e.ctrlKey) { e.preventDefault(); } }, { passive: false }); this._host.document.addEventListener('keydown', (e) => { if (this._target && !e.metaKey && !e.ctrlKey) { this._target.select(null); } }); this._host.document.addEventListener('copy', (e) => { const selection = this._host.document.getSelection(); if (!selection || selection.toString().trim() === '') { if (this._target && this._target.selection.size > 0) { const names = []; for (const element of this._target.selection) { if (element.value && element.value.name) { names.push(element.value.name); } } if (names.length > 0) { e.clipboardData.setData('text/plain', names.join('\n')); e.preventDefault(); } } } }); if (this._host.type === 'Electron') { this._host.update({ 'copy.enabled': false }); this._host.document.addEventListener('selectionchange', this._events.selectionchange); } const platform = this._host.environment('platform'); this._menu = new view.Menu(this._host); this._menu.add({ accelerator: platform === 'darwin' ? 'Ctrl+Cmd+F' : 'F11', execute: async () => await this._host.execute('fullscreen') }); this._menu.add({ accelerator: 'Backspace', execute: async () => await this.popTarget() }); if (this._host.environment('menu')) { const menu = this._element('menu'); const button = this._element('menu-button'); this._menu.attach(menu, button); const file = this._menu.group('&File'); file.add({ label: '&Open...', accelerator: 'CmdOrCtrl+O', execute: async () => await this._host.execute('open') }); if (this._host.type === 'Electron') { this._recents = file.group('Open &Recent'); file.add({ label: '&Export...', accelerator: 'CmdOrCtrl+Shift+E', execute: async () => await this._host.execute('export'), enabled: () => this.activeTarget }); file.add({ label: platform === 'darwin' ? '&Close Window' : '&Close', accelerator: 'CmdOrCtrl+W', execute: async () => await this._host.execute('close'), }); file.add({ label: platform === 'win32' ? 'E&xit' : '&Quit', accelerator: platform === 'win32' ? '' : 'CmdOrCtrl+Q', execute: async () => await this._host.execute('quit'), }); } else { file.add({ label: 'Export as &PNG', accelerator: 'CmdOrCtrl+Shift+E', execute: async () => await this.export(`${this._host.document.title}.png`), enabled: () => this.activeTarget }); file.add({ label: 'Export as &SVG', accelerator: 'CmdOrCtrl+Alt+E', execute: async () => await this.export(`${this._host.document.title}.svg`), enabled: () => this.activeTarget }); } const edit = this._menu.group('&Edit'); edit.add({ label: '&Find...', accelerator: 'CmdOrCtrl+F', execute: () => this.find(), enabled: () => this.activeTarget }); const view = this._menu.group('&View'); view.add({ label: () => this.options.attributes ? 'Hide &Attributes' : 'Show &Attributes', accelerator: 'CmdOrCtrl+D', execute: () => this.toggle('attributes'), enabled: () => this.activeTarget }); view.add({ label: () => this.options.weights ? 'Hide &Weights' : 'Show &Weights', accelerator: 'CmdOrCtrl+I', execute: () => this.toggle('weights'), enabled: () => this.activeTarget }); view.add({ label: () => this.options.names ? 'Hide &Names' : 'Show &Names', accelerator: 'CmdOrCtrl+U', execute: () => this.toggle('names'), enabled: () => this.activeTarget }); view.add({ label: () => this.options.direction === 'vertical' ? 'Show &Horizontal' : 'Show &Vertical', accelerator: 'CmdOrCtrl+K', execute: () => this.toggle('direction'), enabled: () => this.activeTarget }); view.add({ label: () => this.options.mousewheel === 'scroll' ? '&Mouse Wheel: Zoom' : '&Mouse Wheel: Scroll', accelerator: 'CmdOrCtrl+M', execute: () => this.toggle('mousewheel'), enabled: () => this.activeTarget }); view.add({}); if (this._host.type === 'Electron') { view.add({ label: '&Reload', accelerator: platform === 'darwin' ? 'CmdOrCtrl+R' : 'F5', execute: async () => await this._host.execute('reload'), enabled: () => this.activeTarget }); view.add({}); } view.add({ label: 'Zoom &In', accelerator: 'Shift+Up', execute: () => this.zoomIn(), enabled: () => this.activeTarget && this.target }); view.add({ label: 'Zoom &Out', accelerator: 'Shift+Down', execute: () => this.zoomOut(), enabled: () => this.activeTarget && this.target }); view.add({ label: 'Actual &Size', accelerator: 'Shift+Backspace', execute: () => this.resetZoom(), enabled: () => this.activeTarget && this.target }); view.add({}); view.add({ label: '&Properties...', accelerator: 'CmdOrCtrl+Enter', execute: () => this.showTargetProperties(), enabled: () => this.activeTarget }); if (this._host.type === 'Electron' && !this._host.environment('packaged')) { view.add({}); view.add({ label: '&Developer Tools...', accelerator: 'CmdOrCtrl+Alt+I', execute: async () => await this._host.execute('toggle-developer-tools') }); } // const help = this._menu.group('&Help'); // help.add({ // label: 'Report &Issue', // execute: async () => await this._host.execute('report-issue') // }); // help.add({ // label: `&About ${this._host.environment('name')}`, // execute: async () => await this._host.execute('about') // }); } const navigator = this._element('toolbar-navigator'); this._select = new view.TargetSelector(this, navigator); this._select.on('change', (sender, target) => this._updateActiveTarget([target])); await this._host.start(); } catch (error) { this.error(error, null, null); } } dispose() { if (this._worker) { this._worker.cancel(true); } } get host() { return this._host; } show(page) { if (!page) { page = (!this._model && !this.activeTarget) ? 'welcome' : 'default'; } this._host.event('screen_view', { screen_name: page, }); if (this._sidebar) { this._sidebar.close(); } if (this._menu) { this._menu.close(); } this._host.document.body.classList.remove(...Array.from(this._host.document.body.classList).filter((_) => _ !== 'active')); this._host.document.body.classList.add(...page.split(' ')); if (this._target && page === 'default') { this._target.register(); } else if (this._target) { this._target.unregister(); } if (page === 'welcome') { const element = this._element('open-file-button'); if (element) { element.focus(); } } this._page = page; } progress(percent) { const bar = this._element('progress-bar'); if (bar) { bar.style.width = `${percent}%`; } } find() { if (this._target && this._sidebar.identifier !== 'find') { this._target.select(null); const sidebar = new view.FindSidebar(this, this._find, this.activeTarget, this.activeSignature); sidebar.on('state-changed', (sender, state) => { this._find = state; }); sidebar.on('select', (sender, value) => { this._target.scrollTo(this._target.select([value])); }); sidebar.on('focus', (sender, value) => { this._target.focus([value]); }); sidebar.on('blur', (sender, value) => { this._target.blur([value]); }); sidebar.on('activate', (sender, value) => { this._sidebar.close(); this._target.scrollTo(this._target.activate(value)); }); this._sidebar.open(sidebar, 'Find'); } } get model() { return this._model; } set model(value) { this._model = value; } get options() { return this._options; } get target() { return this._target; } set target(value) { if (this._target !== value) { if (this._target) { this._target.off('selectionchange', this._events.selectionchange); this._target.unregister(); } const enabled = value ? true : false; this._host.update({ 'zoom-reset.enabled': enabled, 'zoom-in.enabled': enabled, 'zoom-out.enabled': enabled }); this._target = value; if (this._target) { this._target.on('selectionchange', this._events.selectionchange); this._target.register(); } } } toggle(name) { switch (name) { case 'names': case 'attributes': case 'weights': this._options[name] = !this._options[name]; this._reload(); break; case 'direction': this._options.direction = this._options.direction === 'vertical' ? 'horizontal' : 'vertical'; this._reload(); break; case 'mousewheel': this._options.mousewheel = this._options.mousewheel === 'scroll' ? 'zoom' : 'scroll'; break; default: throw new view.Error(`Unsupported toggle '${name}'.`); } const options = {}; for (const [name, value] of Object.entries(this._options)) { if (this._defaultOptions[name] !== value) { options[name] = value; } } if (Object.entries(options).length === 0) { this._host.delete('options'); } else { this._host.set('options', options); } } recents(recents) { if (this._recents) { this._recents.clear(); for (let i = 0; i < recents.length; i++) { const recent = recents[i]; this._recents.add({ label: recent.label, accelerator: `CmdOrCtrl+${(i + 1)}`, execute: () => this._host.execute('open', recent.path) }); } } } _reload() { this.show('welcome spinner'); if (this._model && this._path.length > 0) { this._updateTarget(this._model, this._path).catch((error) => { if (error) { this.error(error, 'Graph update failed.', 'welcome'); } }); } } _timeout(delay) { return new Promise((resolve) => { this._host.window.setTimeout(resolve, delay); }); } _selectionChangeHandler() { if (this._host.type === 'Electron') { const selection = this._host.document.getSelection(); const text = selection.rangeCount === 0 || selection.toString().trim() !== ''; const graph = this._target && this._target.selection.size > 0; this._host.update({ 'copy.enabled': text || graph }); } } _element(id) { return this._host.document.getElementById(id); } zoomIn() { this._target.zoom *= 1.1; } zoomOut() { this._target.zoom *= 0.9; } resetZoom() { this._target.zoom = 1; } async refresh(anchor) { const snapshot = new Map(); if (this._target) { for (const [key, entry] of this._target.nodes) { const label = entry.label; if (label && label.x !== undefined) { snapshot.set(label.value || key, { x: label.x, y: label.y, width: label.width || 0, height: label.height || 0 }); } } } const document = this._host.document; const container = document.getElementById('target'); const zoom = this._target ? this._target._zoom : 1; const blocks = this._target ? this._target.blocks : null; if (blocks && blocks.size > 0 && this._path.length > 0) { this._path[0].state = Object.assign(this._path[0].state || {}, { blocks }); } const origin = document.getElementById('origin'); let previous = null; if (origin && this.activeTarget) { previous = origin.getScreenCTM(); const oldChildren = Array.from(origin.children); const graph = this.activeTarget; const groups = graph.groups || false; const viewGraph = new view.Graph(this, groups); const state = this._path && this._path.length > 0 && this._path[0] ? this._path[0].state : null; if (state && state.blocks) { viewGraph.blocks = state.blocks; } viewGraph.add(graph, this.activeSignature); viewGraph.build(document, origin); await viewGraph.measure(); const status = await viewGraph.layout(this._worker); if (status === '') { for (const child of oldChildren) { if (child.parentNode === origin) { origin.removeChild(child); } } viewGraph.update(); origin.setAttribute('transform', 'translate(0,0) scale(1)'); document.getElementById('background').setAttribute('width', 0); document.getElementById('background').setAttribute('height', 0); viewGraph.restore(state); this.target = viewGraph; } else { for (const child of Array.from(origin.children)) { if (!oldChildren.includes(child)) { origin.removeChild(child); } } } } else { await this.render(this.activeTarget, this.activeSignature); } this.show(null); if (this._target) { this._target.zoom = zoom; if (container && anchor) { const anchorNode = this._target.find(anchor.value); if (anchorNode instanceof grapher.Node && anchorNode.element) { let newRect = anchorNode.element.getBoundingClientRect(); if (anchorNode.definition && anchorNode.definition.element) { newRect = anchorNode.definition.element.getBoundingClientRect(); } if (container.scrollWidth > container.clientWidth) { container.scrollLeft += (newRect.left - anchor.rect.left); } if (container.scrollHeight > container.clientHeight) { container.scrollTop += (newRect.top - anchor.rect.top); } } delete this._target._scrollLeft; delete this._target._scrollTop; } const current = origin ? origin.getScreenCTM() : null; const ox = previous && current ? (previous.e - current.e) / current.a : 0; const oy = previous && current ? (previous.f - current.f) / current.d : 0; const animateTransition = (snapshot) => { if (!this._target || snapshot.size === 0) { return; } const duration = 300; let startTime = 0; const animations = []; for (const [key, entry] of this._target.nodes) { const label = entry.label; if (!label || !label.element) { continue; } const modelKey = label.value || key; const old = snapshot.get(modelKey); const isCluster = this._target.children(key).length > 0; if (old) { if (isCluster) { animations.push({ type: 'cluster', element: label.element, rect: label.rectangle, fromX: old.x + ox, fromY: old.y + oy, toX: label.x, toY: label.y, fromW: old.width, fromH: old.height, toW: label.width, toH: label.height }); } else { const fw = old.width; const fh = old.height; const tw = label.width; const th = label.height; animations.push({ type: 'node', element: label.element, fromX: old.x - fw / 2 + ox, fromY: old.y - fh / 2 + oy, toX: label.x - tw / 2, toY: label.y - th / 2 }); } } else { label.element.style.opacity = '0'; animations.push({ type: 'fadein', element: label.element }); } } for (const edge of this._target.edges.values()) { const label = edge.label; if (!label || !label.element) { continue; } const fromNode = snapshot.get(label.from.value || edge.v); const toNode = snapshot.get(label.to.value || edge.w); if (fromNode && toNode) { const newFrom = this._target.node(edge.v); const newTo = this._target.node(edge.w); if (newFrom && newTo) { const dfx = fromNode.x - newFrom.label.x; const dfy = fromNode.y - newFrom.label.y; const dtx = toNode.x - newTo.label.x; const dty = toNode.y - newTo.label.y; const edgeOx = (dfx + dtx) / 2 + ox; const edgeOy = (dfy + dty) / 2 + oy; if (Math.abs(edgeOx) > 0.5 || Math.abs(edgeOy) > 0.5) { const labelTransform = label.labelElement ? label.labelElement.getAttribute('transform') : null; animations.push({ type: 'edge', element: label.element, hitTest: label.hitTest, labelElement: label.labelElement, labelTransform, fromX: edgeOx, fromY: edgeOy }); } } } else { label.element.style.opacity = '0'; animations.push({ type: 'fadein', element: label.element }); if (label.hitTest) { label.hitTest.style.opacity = '0'; animations.push({ type: 'fadein', element: label.hitTest }); } if (label.labelElement) { label.labelElement.style.opacity = '0'; animations.push({ type: 'fadein', element: label.labelElement }); } } } for (const anim of animations) { if (anim.type === 'node') { anim.element.setAttribute('transform', `translate(${anim.fromX},${anim.fromY})`); } else if (anim.type === 'cluster') { anim.element.setAttribute('transform', `translate(${anim.fromX},${anim.fromY})`); if (anim.rect) { anim.rect.setAttribute('x', -anim.fromW / 2); anim.rect.setAttribute('y', -anim.fromH / 2); anim.rect.setAttribute('width', anim.fromW); anim.rect.setAttribute('height', anim.fromH); } } else if (anim.type === 'edge') { const t = `translate(${anim.fromX},${anim.fromY})`; anim.element.setAttribute('transform', t); if (anim.hitTest) { anim.hitTest.setAttribute('transform', t); } if (anim.labelElement) { anim.labelElement.setAttribute('transform', `translate(${anim.fromX},${anim.fromY}) ${anim.labelTransform || ''}`); } } else if (anim.type === 'fadein') { anim.element.style.opacity = '0'; } } const tick = (now) => { if (!startTime) { startTime = now; } const elapsed = now - startTime; const t = Math.min(elapsed / duration, 1); const ease = 1 - Math.pow(1 - t, 3); for (const anim of animations) { if (anim.type === 'node') { const x = anim.fromX + (anim.toX - anim.fromX) * ease; const y = anim.fromY + (anim.toY - anim.fromY) * ease; anim.element.setAttribute('transform', `translate(${x},${y})`); } else if (anim.type === 'cluster') { const x = anim.fromX + (anim.toX - anim.fromX) * ease; const y = anim.fromY + (anim.toY - anim.fromY) * ease; anim.element.setAttribute('transform', `translate(${x},${y})`); if (anim.rect) { const w = anim.fromW + (anim.toW - anim.fromW) * ease; const h = anim.fromH + (anim.toH - anim.fromH) * ease; anim.rect.setAttribute('x', -w / 2); anim.rect.setAttribute('y', -h / 2); anim.rect.setAttribute('width', w); anim.rect.setAttribute('height', h); } } else if (anim.type === 'edge') { const x = anim.fromX * (1 - ease); const y = anim.fromY * (1 - ease); const tr = `translate(${x},${y})`; anim.element.setAttribute('transform', tr); if (anim.hitTest) { anim.hitTest.setAttribute('transform', tr); } if (anim.labelElement) { anim.labelElement.setAttribute('transform', `translate(${x},${y}) ${anim.labelTransform || ''}`); } } else if (anim.type === 'fadein') { anim.element.style.opacity = String(ease); } } if (t < 1) { this._host.window.requestAnimationFrame(tick); } else { for (const anim of animations) { if (anim.type === 'fadein') { anim.element.style.removeProperty('opacity'); } else if (anim.type === 'edge') { anim.element.removeAttribute('transform'); if (anim.hitTest) { anim.hitTest.removeAttribute('transform'); } if (anim.labelElement) { if (anim.labelTransform) { anim.labelElement.setAttribute('transform', anim.labelTransform); } else { anim.labelElement.removeAttribute('transform'); } } } } } }; this._host.window.requestAnimationFrame(tick); }; animateTransition(snapshot); } } async error(error, name, screen) { if (this._sidebar) { this._sidebar.close(); } this.exception(error, false); const repository = this._host.environment('repository'); const knowns = [ { message: /^Invalid value identifier/, issue: '540' }, { message: /^Cannot read property/, issue: '647' }, { message: /^Duplicate value /, issue: '1364' }, { message: /^EPERM: operation not permitted/, issue: '551' }, { message: /^EACCES: permission denied/, issue: '504' }, { message: /^Offset is outside the bounds of the DataView/, issue: '563' }, { message: /^Invalid string length/, issue: '648' }, { message: /^Unknown function /, issue: '546' }, { message: /^Unsupported file content/, issue: '550' }, { message: /^Unsupported Protocol Buffers content/, issue: '593' }, { message: /^Unsupported Protocol Buffers text content/, issue: '594' }, { message: /^Unsupported JSON content/, issue: '595' }, { message: /^Unknown type name '__torch__\./, issue: '969' }, { name: 'Error loading ONNX model.', message: /^File format is not onnx\.ModelProto \(Unexpected end of file\)\./, issue: '1155' }, { name: 'Error loading ONNX model.', message: /^File format is not onnx\.ModelProto \(Cannot read properties of undefined \(reading 'ModelProto'\)\)\./, issue: '1156' }, { name: 'Error loading ONNX model.', message: /^File format is not onnx\.ModelProto/, issue: '549' } ]; const known = knowns.find((known) => (!known.name || known.name === error.name) && error.message.match(known.message)); const url = known && known.issue ? `${repository}/issues/${known.issue}` : `${repository}/issues`; const message = error.message; name = name || error.name; const report = !message.startsWith('Invalid file content.') && this.host.environment('packaged'); await this._host.message(message, true, report ? 'Report' : 'OK'); if (report) { this._host.openURL(url); } this.show(screen); } accept(file, size) { return this._modelFactoryService.accept(file, size); } async open(context) { this._sidebar.close(); await this._timeout(2); try { const model = await this._modelFactoryService.open(context); const format = []; if (model.format) { format.push(model.format); } if (model.producer) { format.push(`(${model.producer})`); } if (format.length > 0) { this._host.event('model_open', { model_format: model.format || '', model_producer: model.producer || '' }); } await this._timeout(20); const path = []; const modules = Array.isArray(model.functions) ? model.modules.concat(model.functions) : model.modules; let target = modules.length > 0 ? modules[0] : null; for (const module of modules) { if (Array.isArray(module.nodes) && module.nodes.length > 0) { target = module; break; } } if (target) { const signature = Array.isArray(target.signatures) && target.signatures.length > 0 ? target.signatures[0] : null; path.push({ target, signature }); } return await this._updateTarget(model, path); } catch (error) { error.context = !error.context && context && context.identifier ? context.identifier : error.context || ''; throw error; } } async attach(context) { if (this._model) { const attachment = new metadata.Attachment(); if (await attachment.open(context)) { this._model.attachment = attachment; return true; } } return false; } async _updateActiveTarget(stack) { this._sidebar.close(); if (this._model) { this.show('welcome spinner'); try { await this._updateTarget(this._model, stack); } catch (error) { if (error) { this.error(error, 'Graph update failed.', 'welcome'); } } } } get activeTarget() { if (this._path.length > 0) { return this._path[0].target; } return null; } get activeSignature() { if (this._path.length > 0) { return this._path[0].signature; } return null; } async _updateTarget(model, path) { const lastModel = this._model; const lastPath = this._path; try { await this._updatePath(model, path); return this._model; } catch (error) { await this._updatePath(lastModel, lastPath); throw error; } } async _updatePath(model, stack) { this.model = model; this._path = stack; const status = await this.render(this.activeTarget, this.activeSignature); if (status === 'cancel') { this.model = null; this._path = []; this._activeTarget = null; } this.show(null); const path = this._element('toolbar-path'); const back = this._element('toolbar-path-back-button'); while (path.children.length > 1) { path.removeChild(path.lastElementChild); } if (status === '') { if (this._path.length <= 1) { back.style.opacity = 0; } else { back.style.opacity = 1; const last = this._path.length - 2; const count = Math.min(2, last); const document = this.host.document; if (count < last) { const element = document.createElement('button'); element.setAttribute('class', 'toolbar-path-name-button'); element.innerHTML = '…'; path.appendChild(element); } for (let i = count; i >= 0; i--) { const target = this._path[i].target; const element = document.createElement('button'); element.setAttribute('class', 'toolbar-path-name-button'); element.addEventListener('click', async () => { if (i > 0) { this._path = this._path.slice(i); await this._updateTarget(this._model, this._path); } else { await this.showTargetProperties(target); } }); let name = ''; if (target && target.identifier) { name = target.identifier; } else if (target && target.name) { name = target.name; } if (name.length > 24) { element.setAttribute('title', name); const truncated = name.substring(name.length - 24, name.length); element.innerHTML = '…'; const text = document.createTextNode(truncated); element.appendChild(text); } else { element.removeAttribute('title'); if (name) { element.textContent = name; } else { element.innerHTML = ' '; } } path.appendChild(element); } } this._select.update(model, stack); const button = this._element('sidebar-target-button'); if (stack.length > 0) { const type = stack[stack.length - 1].type || 'graph'; const name = type.charAt(0).toUpperCase() + type.slice(1); button.setAttribute('title', `${name} Properties`); button.style.display = 'block'; } else { button.style.display = 'none'; } } } async pushTarget(graph, context) { if (graph && graph !== this.activeTarget && Array.isArray(graph.nodes)) { this._sidebar.close(); if (context && this._path.length > 0) { this._path[0].state = { context, zoom: this._target.zoom, blocks: this._target.blocks }; } const signature = Array.isArray(graph.signatures) && graph.signatures.length > 0 ? graph.signatures[0] : null; const entry = { target: graph, signature }; const stack = [entry].concat(this._path); await this._updateTarget(this._model, stack); } } async popTarget() { if (this._path.length > 1) { this._sidebar.close(); return await this._updateTarget(this._model, this._path.slice(1)); } return null; } async render(target, signature) { this.target = null; const element = this._element('target'); while (element.lastChild) { element.removeChild(element.lastChild); } let status = ''; if (target) { const document = this._host.document; const graph = target; const groups = graph.groups || false; const nodes = graph.nodes; this._host.event('graph_view', { graph_node_count: nodes.length, graph_skip: 0 }); const viewGraph = new view.Graph(this, groups); const state = this._path && this._path.length > 0 && this._path[0] ? this._path[0].state : null; if (state && state.blocks) { viewGraph.blocks = state.blocks; } viewGraph.add(graph, signature); viewGraph.build(document); await viewGraph.measure(); status = await viewGraph.layout(this._worker); if (status === '') { viewGraph.update(); viewGraph.restore(state); this.target = viewGraph; } } return status; } async export(file) { const window = this.host.window; const lastIndex = file.lastIndexOf('.'); const extension = lastIndex === -1 ? 'png' : file.substring(lastIndex + 1).toLowerCase(); if (this.activeTarget && (extension === 'png' || extension === 'svg')) { const canvas = this._element('canvas'); const clone = canvas.cloneNode(true); const document = this._host.document; const applyStyleSheet = (element, name) => { let rules = []; for (const styleSheet of document.styleSheets) { if (styleSheet && styleSheet.href && styleSheet.href.endsWith(`/${name}`)) { rules = styleSheet.cssRules; break; } } const nodes = element.getElementsByTagName('*'); for (const node of nodes) { for (const rule of rules) { if (node.matches(rule.selectorText)) { for (const item of rule.style) { node.style[item] = rule.style[item]; } } } } }; applyStyleSheet(clone, 'grapher.css'); clone.setAttribute('id', 'export'); clone.removeAttribute('viewBox'); clone.removeAttribute('width'); clone.removeAttribute('height'); clone.style.removeProperty('opacity'); clone.style.removeProperty('display'); clone.style.removeProperty('width'); clone.style.removeProperty('height'); const background = clone.querySelector('#background'); clone.getElementById('edge-paths-hit-test').remove(); const origin = clone.querySelector('#origin'); origin.setAttribute('transform', 'translate(0,0) scale(1)'); background.removeAttribute('width'); background.removeAttribute('height'); const parent = canvas.parentElement; parent.insertBefore(clone, canvas); const size = clone.getBBox(); parent.removeChild(clone); parent.removeChild(canvas); parent.appendChild(canvas); const delta = (Math.min(size.width, size.height) / 2.0) * 0.1; const width = Math.ceil(delta + size.width + delta); const height = Math.ceil(delta + size.height + delta); origin.setAttribute('transform', `translate(${(delta - size.x)}, ${(delta - size.y)}) scale(1)`); clone.setAttribute('width', width); clone.setAttribute('height', height); background.setAttribute('width', width); background.setAttribute('height', height); background.setAttribute('fill', '#fff'); const data = new window.XMLSerializer().serializeToString(clone); if (extension === 'svg') { const blob = new window.Blob([data], { type: 'image/svg' }); await this._host.export(file, blob); } if (extension === 'png') { const blob = await new Promise((resolve, reject) => { this.show('welcome spinner'); this.progress(0); const image = new window.Image(); image.onload = async () => { try { let targetWidth = Math.ceil(width * 2); let targetHeight = Math.ceil(height * 2); let scale = 1; if (targetWidth > 100000 || targetHeight > 100000) { scale = Math.min(scale, 100000 / Math.max(targetWidth, targetHeight)); } if (targetWidth * targetHeight * scale * scale > 500000000) { scale = Math.min(scale, Math.sqrt(500000000 / (targetWidth * targetHeight))); } if (scale < 1) { targetWidth = Math.floor(targetWidth * scale); targetHeight = Math.floor(targetHeight * scale); } const drawScale = targetWidth / width; const size = Math.min(targetWidth, 4096); const encoder = new png.Encoder(window, targetWidth, targetHeight); const canvas = this._host.document.createElement('canvas'); canvas.width = size; canvas.height = 4096; const context = canvas.getContext('2d'); for (let y = 0; y < targetHeight; y += 4096) { const h = Math.min(4096, targetHeight - y); const data = new Uint8Array(targetWidth * h * 4); for (let x = 0; x < targetWidth; x += size) { const w = Math.min(size, targetWidth - x); context.setTransform(drawScale, 0, 0, drawScale, -x, -y); context.drawImage(image, 0, 0); const tileData = context.getImageData(0, 0, w, h); for (let row = 0; row < h; row++) { const src = row * w * 4; const dst = row * targetWidth * 4 + x * 4; data.set(tileData.data.subarray(src, src + w * 4), dst); } } /* eslint-disable-next-line no-await-in-loop */ await encoder.write(data, h); this.progress((y + h) / targetHeight * 100); } const buffer = await encoder.toBuffer(); this.progress(0); this.show('default'); resolve(new window.Blob([buffer], { type: 'image/png' })); } catch (error) { this.progress(0); this.show('default'); reject(error); } }; image.onerror = (error) => { this.progress(0); this.show('default'); reject(error); }; image.src = `data:image/svg+xml;base64,${this._host.window.btoa(unescape(encodeURIComponent(data)))}`; }); await this._host.export(file, blob); } } } showModelProperties() { if (!this._model) { return; } try { const sidebar = new view.ModelSidebar(this, this.model); this._sidebar.open(sidebar, 'Model Properties'); } catch (error) { this.error(error, 'Error showing model properties.', null); } } showTargetProperties(target) { if (this._sidebar.identifier === 'target' && !target) { this.showModelProperties(); return; } target = target || this.activeTarget; if (!target) { return; } try { const sidebar = new view.TargetSidebar(this, target, this.activeSignature); sidebar.on('show-definition', async (/* sender, e */) => { await this.showDefinition(target); }); sidebar.on('focus', (sender, value) => { this._target.focus([value]); }); sidebar.on('blur', (sender, value) => { this._target.blur([value]); }); sidebar.on('select', (sender, value) => { this._target.scrollTo(this._target.select([value])); }); sidebar.on('activate', (sender, value) => { this._target.scrollTo(this._target.activate(value)); }); sidebar.on('deactivate', () => { this._target.select(null); }); let title = null; const type = target.type || 'graph'; switch (type) { case 'graph': title = 'Graph Properties'; break; case 'function': title = 'Function Properties'; break; case 'weights': title = 'Weights Properties'; break; default: throw new view.Error(`Unsupported graph type '${type}'.`); } this._sidebar.open(sidebar, title); } catch (error) { this.error(error, 'Error showing target properties.', null); } } showNodeProperties(node) { if (node) { try { if (this._menu) { this._menu.close(); } const sidebar = new view.NodeSidebar(this, node); sidebar.on('show-definition', async (/* sender, e */) => { await this.showDefinition(node.type); }); sidebar.on('focus', (sender, value) => { this._target.focus([value]); }); sidebar.on('blur', (sender, value) => { this._target.blur([value]); }); sidebar.on('select', (sender, value) => { this._target.scrollTo(this._target.select([value])); }); sidebar.on('activate', (sender, value) => { this._target.scrollTo(this._target.activate(value)); }); this._sidebar.open(sidebar, 'Node Properties'); } catch (error) { this.error(error, 'Error showing node properties.', null); } } } showConnectionProperties(value, from, to) { try { if (this._menu) { this._menu.close(); } const sidebar = new view.ConnectionSidebar(this, value, from, to); sidebar.on('focus', (sender, value) => { this._target.focus([value]); }); sidebar.on('blur', (sender, value) => { this._target.blur([value]); }); sidebar.on('select', (sender, value) => { this._target.scrollTo(this._target.select([value])); }); sidebar.on('activate', (sender, value) => { this._target.scrollTo(this._target.activate(value)); }); this._sidebar.push(sidebar, 'Connection Properties'); } catch (error) { this.error(error, 'Error showing connection properties.', null); } } showTensorProperties(value) { try { if (this._menu) { this._menu.close(); } const sidebar = new view.TensorSidebar(this, value); sidebar.on('focus', (sender, value) => { this._target.focus([value]); }); sidebar.on('blur', () => { this._target.blur(null); }); sidebar.on('select', (sender, value) => { this._target.scrollTo(this._target.select([value])); }); sidebar.on('activate', (sender, value) => { this._target.scrollTo(this._target.activate(value)); }); this._sidebar.push(sidebar, 'Tensor Properties'); } catch (error) { this.error(error, 'Error showing tensor properties.', null); } } exception(error, fatal) { if (error && !error.context && this._model && this._model.identifier) { error.context = this._model.identifier; } this._host.exception(error, fatal); } async showDefinition(type) { if (type && (type.description || type.inputs || type.outputs || type.attributes)) { if (type.nodes && type.nodes.length > 0) { await this.pushTarget(type); } if (type.type !== 'weights') { const sidebar = new view.DocumentationSidebar(this, type); sidebar.on('navigate', (sender, e) => { this._host.openURL(e.link); }); const title = type.type === 'function' ? 'Function Documentation' : 'Documentation'; this._sidebar.push(sidebar, title); } } } about() { this._host.document.getElementById('version').innerText = this._host.version; const handler = () => { this._host.window.removeEventListener('keydown', handler); this._host.document.body.removeEventListener('click', handler); this._host.document.body.classList.remove('about'); }; this._host.window.addEventListener('keydown', handler); this._host.document.body.addEventListener('click', handler); this._host.document.body.classList.add('about'); } }; view.Menu = class { constructor(host) { this.items = []; this._darwin = host.environment('platform') === 'darwin'; this._document = host.document; this._window = host.window; this._stack = []; this._root = []; this._buttons = []; this._accelerators = new Map(); this._keyCodes = new Map([ ['Backspace', 0x08], ['Enter', 0x0D], ['Escape', 0x1B], ['Left', 0x25], ['Up', 0x26], ['Right', 0x27], ['Down', 0x28], ['F5', 0x74], ['F11', 0x7a] ]); this._symbols = new Map([ ['Backspace', '⌫'], ['Enter', '⏎'], ['Up', '↑'], ['Down', '↓'], ]); this._keydown = (e) => { this._alt = false; const code = e.keyCode | (e.altKey ? 0x0200 : 0) | (e.shiftKey ? 0x0100 : 0); const modifier = (e.ctrlKey ? 0x0400 : 0) | (e.metaKey ? 0x0800 : 0); if ((code | modifier) === 0x0212) { // Alt this._alt = true; } else { const action = this._accelerators.get(code | modifier) || this._accelerators.get(code | ((e.ctrlKey && !this._darwin) || (e.metaKey && this._darwin) ? 0x1000 : 0)); if (action && this._execute(action)) { e.preventDefault(); } else { const item = this._mnemonic(code | modifier); if (item && this._activate(item)) { e.preventDefault(); } } } }; this._keyup = (e) => { if (e.keyCode === 0x0012 && this._alt) { // Alt if (this._stack.length === 0) { if (this.open()) { e.preventDefault(); } } else if (this._stack.length === 1) { if (this.close()) { e.preventDefault(); } } else { this._stack = [this]; if (this._root.length > 1) { this._root = [this]; this._rebuild(); } this._update(); e.preventDefault(); } } this._alt = false; }; this._next = () => { const button = this._element.ownerDocument.activeElement; const index = this._buttons.indexOf(button); if (index !== -1 && index < this._buttons.length - 1) { const next = this._buttons[index + 1]; next.focus(); } }; this._previous = () => { const button = this._element.ownerDocument.activeElement; const index = this._buttons.indexOf(button); if (index > 0) { const next = this._buttons[index - 1]; next.focus(); } }; this._push = () => { const button = this._element.ownerDocument.activeElement; if (button && button.getAttribute('data-type') === 'group') { button.click(); } }; this._pop = () => { if (this._stack.length > 1) { this._deactivate(); } }; this._exit = () => { this._deactivate(); if (this._stack.length === 0) { this.close(); } }; host.window.addEventListener('keydown', this._keydown); host.window.addEventListener('keyup', this._keyup); } attach(element, button) { this._element = element; button.addEventListener('click', (e) => { this.toggle(); e.preventDefault(); }); } add(value) { const item = new view.Menu.Command(value); this.register(item, item.accelerator); } group(label) { const item = new view.Menu.Group(this, label); item.identifier = `menu-item-${this.items.length}`; this.items.push(item); item.shortcut = this.register(item.accelerator); return item; } toggle() { if (this._element.style.opacity >= 1) { this.close(); } else { this._root = [this]; this._stack = [this]; this.open(); } } open() { if (this._element) { if (this._stack.length === 0) { this.toggle(); this._stack = [this]; } this._rebuild(); this._update(); this.register(this._exit, 'Escape'); this.register(this._previous, 'Up'); this.register(this._next, 'Down'); this.register(this._pop, 'Left'); this.register(this._push, 'Right'); } } close() { if (this._element) { this.unregister(this._exit); this.unregister(this._previous); this.unregister(this._next); this.unregister(this._pop); this.unregister(this._push); this._element.style.opacity = 0; this._element.style.left = '-17em'; const button = this._element.ownerDocument.activeElement; if (this._buttons.indexOf(button) > 0) { button.blur(); } while (this._root.length > 1) { this._deactivate(); } this._stack = []; } } register(action, accelerator) { let shortcut = ''; if (accelerator) { let shift = false; let alt = false; let ctrl = false; let cmd = false; let cmdOrCtrl = false; let key = ''; for (const part of accelerator.split('+')) { switch (part) { case 'CmdOrCtrl': cmdOrCtrl = true; break; case 'Cmd': cmd = true; break; case 'Ctrl': ctrl = true; break; case 'Alt': alt = true; break; case 'Shift': shift = true; break; default: key = part; break; } } if (key !== '') { if (this._darwin) { shortcut += ctrl ? '⌃' : ''; shortcut += alt ? '⌥' : ''; shortcut += shift ? '⇧' : ''; shortcut += cmdOrCtrl || cmd ? '⌘' : ''; shortcut += this._symbols.has(key) ? this._symbols.get(key) : key; } else { shortcut += cmdOrCtrl || ctrl ? 'Ctrl+' : ''; shortcut += alt ? 'Alt+' : ''; shortcut += shift ? 'Shift+' : ''; shortcut += key; } let code = (cmdOrCtrl ? 0x1000 : 0) | (cmd ? 0x0800 : 0) | (ctrl ? 0x0400 : 0) | (alt ? 0x0200 : 0) | (shift ? 0x0100 : 0); code |= this._keyCodes.has(key) ? this._keyCodes.get(key) : key.charCodeAt(0); this._accelerators.set(code, action); } } return shortcut; } unregister(action) { this._accelerators = new Map(Array.from(this._accelerators.entries()).filter(([, value]) => value !== action)); } _execute(action) { const window = this._window; if (typeof action === 'function') { action(); return true; } switch (action ? action.type : null) { case 'group': { while (this._stack.length > this._root.length) { this._stack.pop(); } this._root.push({ items: [action] }); this._stack.push(action); this._rebuild(); this._update(); return true; } case 'command': { this.close(); window.setTimeout(() => action.execute(), 10); return true; } default: { return false; } } } _mnemonic(code) { const key = /[a-zA-Z0-9]/.test(String.fromCharCode(code & 0x00FF)); const modifier = (code & 0xFF00) !== 0; const alt = (code & 0xFF00) === 0x0200; if (alt && key) { this.open(); } if (this._stack.length > 0 && key && (alt || !modifier)) { const key = String.fromCharCode(code & 0x00FF); const group = this._stack.length > 0 ? this._stack[this._stack.length - 1] : this; const item = group.items.find((item) => key === item.mnemonic && (item.type === 'group' || item.type === 'command') && item.enabled); if (item) { return item; } } return null; } _activate(item) { switch (item ? item.type : null) { case 'group': { this._stack.push(item); this._rebuild(); this._update(); return true; } case 'command': { return this._execute(item); } default: { return false; } } } _deactivate() { if (this._root.length > 1) { this._root.pop(); const group = this._stack.pop(); this._rebuild(); this._update(); if (group) { const button = this._buttons.find((button) => button.getAttribute('id') === group.identifier); if (button) { button.focus(); } } } else if (this._stack.length > 0) { this._stack.pop(); this._update(); } } _label(item, mnemonic) { delete item.mnemonic; const value = item.label; if (value) { const index = value.indexOf('&'); if (index !== -1) { if (mnemonic) { item.mnemonic = value[index + 1].toUpperCase(); return `${value.substring(0, index)}${value[index + 1]}${value.substring(index + 2)}`; } return value.substring(0, index) + value.substring(index + 1); } } return value || ''; } _rebuild() { this._element.replaceChildren(); const root = this._root[this._root.length - 1]; for (const group of root.items) { const container = this._document.createElement('div'); container.setAttribute('id', group.identifier); container.setAttribute('class', 'menu-group'); container.innerHTML = "
"; for (const item of group.items) { switch (item.type) { case 'group': case 'command': { const button = this._document.createElement('button'); button.setAttribute('class', 'menu-command'); button.setAttribute('id', item.identifier); button.setAttribute('data-type', item.type); button.addEventListener('mouseenter', () => button.focus()); button.addEventListener('click', () => this._execute(item)); const accelerator = this._document.createElement('span'); accelerator.setAttribute('class', 'menu-shortcut'); if (item.type === 'group') { accelerator.innerHTML = '❯'; } else if (item.shortcut) { accelerator.innerHTML = item.shortcut; } button.appendChild(accelerator); const content = this._document.createElement('span'); content.setAttribute('class', 'menu-label'); button.appendChild(content); container.appendChild(button); break; } case 'separator': { const element = this._document.createElement('div'); element.setAttribute('class', 'menu-separator'); element.setAttribute('id', item.identifier); container.appendChild(element); break; } default: { break; } } } this._element.appendChild(container); } this._element.style.opacity = 1.0; this._element.style.left = '0px'; if (this._root.length > 1) { this._element.style.width = 'auto'; this._element.style.maxWidth = '60%'; } else { this._element.style.removeProperty('width'); this._element.style.maxWidth = 'auto'; } } _update() { this._buttons = []; const selected = this._stack.length > 0 ? this._stack[this._stack.length - 1] : null; const root = this._root[this._root.length - 1]; for (const group of root.items) { let visible = false; let block = false; const active = this._stack.length <= 1 || this._stack[1] === group; const container = this._document.getElementById(group.identifier); container.childNodes[0].innerHTML = this._label(group, this === selected); for (const item of group.items) { switch (item.type) { case 'group': case 'command': { const label = this._label(item, group === selected); const button = this._document.getElementById(item.identifier); button.childNodes[1].innerHTML = label; if (item.enabled) { button.removeAttribute('disabled'); button.style.display = 'block'; visible = true; block = true; if (active) { this._buttons.push(button); } } else { button.setAttribute('disabled', ''); button.style.display = 'none'; } break; } case 'separator': { const element = this._document.getElementById(item.identifier); element.style.display = block ? 'block' : 'none'; block = false; break; } default: { break; } } } for (let i = group.items.length - 1; i >= 0; i--) { const item = group.items[i]; if ((item.type === 'group' || item.type === 'command') && item.enabled) { break; } else if (item.type === 'separator') { const element = this._document.getElementById(item.identifier); element.style.display = 'none'; } } if (!visible) { container.style.display = 'none'; } container.style.opacity = active ? 1 : 0; } const button = this._element.ownerDocument.activeElement; const index = this._buttons.indexOf(button); if (index === -1 && this._buttons.length > 0) { this._buttons[0].focus(); } } }; view.Menu.Group = class { constructor(parent, label) { this.type = 'group'; this.parent = parent; this.label = label; this.items = []; } get enabled() { return this.items.some((item) => item.enabled); } add(value) { const item = Object.keys(value).length > 0 ? new view.Menu.Command(value) : new view.Menu.Separator(); item.identifier = `${this.identifier}-${this.items.length}`; this.items.push(item); item.shortcut = this.parent.register(item, item.accelerator); } group(label) { const item = new view.Menu.Group(this, label); item.identifier = `${this.identifier}-${this.items.length}`; this.items.push(item); item.shortcut = this.parent.register(item, item.accelerator); return item; } clear() { for (const item of this.items) { if (item.clear) { item.clear(); } this.parent.unregister(item); } this.items = []; } register(item, accelerator) { return this.parent.register(item, accelerator); } unregister(item) { this.parent.unregister(item); } }; view.Menu.Command = class { constructor(item) { this.type = 'command'; this.accelerator = item.accelerator; this._label = item.label; this._enabled = item.enabled; this._execute = item.execute; } get label() { return typeof this._label === 'function' ? this._label() : this._label; } get enabled() { return this._enabled ? this._enabled() : true; } execute() { if (this._execute && this.enabled) { this._execute(); } } }; view.Menu.Separator = class { constructor() { this.type = 'separator'; this.enabled = false; } }; view.Worker = class { constructor(host) { this._host = host; this._timeout = -1; this._create(); } async request(message, delay, notification) { if (this._resolve) { const resolve = this._resolve; resolve({ type: 'terminate' }); delete this._resolve; delete this._reject; this.cancel(true); } else { this.cancel(false); } return new Promise((resolve, reject) => { this._resolve = resolve; this._reject = reject; this._create(); this._worker.postMessage(message); const window = this._host.window; this._timeout = window.setTimeout(async () => { await this._host.message(notification, null, 'Cancel'); this.cancel(true); delete this._resolve; delete this._reject; resolve({ type: 'cancel' }); }, delay); }); } _create() { if (!this._worker) { this._worker = this._host.worker('./worker'); this._worker.addEventListener('message', (e) => { this.cancel(false); const message = e.data; const resolve = this._resolve; const reject = this._reject; delete this._resolve; delete this._reject; if (reject && message.type === 'error') { const error = new Error(`Worker: ${message.message}`); reject(error); } else if (resolve) { resolve(message); } }); this._worker.addEventListener('error', (e) => { this.cancel(true); const reject = this._reject; delete this._resolve; delete this._reject; if (reject) { reject(new Error(`Unknown worker error type '${e.type}'.`)); } }); } } cancel(terminate) { if (this._worker && terminate) { this._worker.terminate(); this._worker = null; } if (this._timeout !== -1) { this._host.window.clearTimeout(this._timeout); this._timeout = -1; this._host.message(); } } }; view.Graph = class extends grapher.Graph { constructor(view, compound) { super(compound); this.view = view; this.counter = 0; this._nodeKey = 0; this._values = new Map(); this._tensors = new Map(); this._table = new Map(); this._selection = new Set(); this.blocks = new Set(); this._zoom = 1; this._listeners = {}; } on(event, callback) { this._listeners[event] = this._listeners[event] || []; this._listeners[event].push(callback); } off(event, callback) { if (this._listeners[event]) { this._listeners[event] = this._listeners[event].filter((c) => c !== callback); } } emit(event, data) { if (this._listeners[event]) { for (const callback of this._listeners[event]) { callback(this, data); } } } get model() { return this.view.model; } get host() { return this.view.host; } get options() { return this.view.options; } get values() { return this._values; } get selection() { return this._selection; } createNode(node) { const obj = new view.Node(this, node); obj.name = (this._nodeKey++).toString(); this._table.set(node, obj); return obj; } createGraph(graph, type) { const obj = new view.Node(this, graph, type || 'graph'); obj.name = (this._nodeKey++).toString(); this._table.set(graph, obj); return obj; } createInput(input) { const obj = new view.Input(this, input); obj.name = (this._nodeKey++).toString(); this._table.set(input, obj); return obj; } createOutput(output) { const obj = new view.Output(this, output); obj.name = (this._nodeKey++).toString(); this._table.set(output, obj); return obj; } createValue(value) { const key = value && value.name && !value.initializer ? value.name : value; if (this._values.has(key)) { // duplicate argument name const obj = this._values.get(key); this._table.set(value, obj); } else { const obj = new view.Value(this, value); this._values.set(key, obj); this._table.set(value, obj); } return this._values.get(key); } createArgument(value) { if (Array.isArray(value.value) && value.value.length === 1 && value.value[0].initializer) { if (!this._tensors.has(value)) { const obj = new view.Argument(this, value); this._tensors.set(value, obj); this._table.set(value, obj); } return this._tensors.get(value); } return null; } find(value) { if (this._table.has(value)) { return this._table.get(value); } for (const obj of this._table.values()) { if (obj instanceof grapher.Node) { for (const block of obj.blocks) { if (block instanceof view.Block) { const found = block.target.find(value); if (found) { return found; } } } } } return null; } add(graph, signature) { this.target = graph; this.identifier = this.model.identifier; this.identifier += graph && graph.name ? `.${graph.name.replace(/\/|\\/g, '.')}` : ''; const clusters = new Set(); const clusterParentMap = new Map(); const groups = graph.groups; if (groups) { for (const node of graph.nodes) { if (node.group) { const path = node.group.split('/'); while (path.length > 0) { const name = path.join('/'); path.pop(); clusterParentMap.set(name, path.join('/')); } } } } const inputs = signature ? signature.inputs : graph.inputs; const outputs = signature ? signature.outputs : graph.outputs; if (Array.isArray(inputs)) { for (const argument of inputs) { if (argument.visible !== false) { const viewInput = this.createInput(argument); this.setNode(viewInput); for (const value of argument.value) { this.createValue(value).from = viewInput; } } } } for (const node of graph.nodes) { const viewNode = this.createNode(node); this.setNode(viewNode); let outputs = node.outputs; if (node.chain && node.chain.length > 0) { const chainOutputs = node.chain[node.chain.length - 1].outputs; if (chainOutputs.length > 0) { outputs = chainOutputs; } } if (Array.isArray(outputs)) { for (const argument of outputs) { for (const value of argument.value) { if (!value) { throw new view.Error('Invalid null argument.'); } if (value.name !== '') { this.createValue(value).from = viewNode; } } } } if (Array.isArray(node.controlDependencies) && node.controlDependencies.length > 0) { for (const value of node.controlDependencies) { this.createValue(value).controlDependency(viewNode); } } const createCluster = (name) => { if (!clusters.has(name)) { this.setNode({ name, rx: 5, ry: 5 }); clusters.add(name); const parent = clusterParentMap.get(name); if (parent) { createCluster(parent); this.setParent(name, parent); } } }; if (groups) { let groupName = node.group; if (groupName && groupName.length > 0) { if (!clusterParentMap.has(groupName)) { const lastIndex = groupName.lastIndexOf('/'); if (lastIndex === -1) { groupName = null; } else { groupName = groupName.substring(0, lastIndex); if (!clusterParentMap.has(groupName)) { groupName = null; } } } if (groupName) { createCluster(`${groupName}\ngroup`); this.setParent(viewNode.name, `${groupName}\ngroup`); } } } } if (Array.isArray(outputs)) { for (const argument of outputs) { if (argument.visible !== false) { const viewOutput = this.createOutput(argument); this.setNode(viewOutput); if (Array.isArray(argument.value)) { for (const value of argument.value) { this.createValue(value).to.push(viewOutput); } } } } } } build(document, origin) { if (!origin) { const element = document.getElementById('target'); while (element.lastChild) { element.removeChild(element.lastChild); } const canvas = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); canvas.setAttribute('id', 'canvas'); canvas.setAttribute('class', 'canvas'); canvas.setAttribute('preserveAspectRatio', 'xMidYMid meet'); canvas.setAttribute('width', '100%'); canvas.setAttribute('height', '100%'); element.appendChild(canvas); // Workaround for Safari background drag/zoom issue: // https://stackoverflow.com/questions/40887193/d3-js-zoom-is-not-working-with-mousewheel-in-safari const background = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); background.setAttribute('id', 'background'); background.setAttribute('fill', 'none'); background.setAttribute('pointer-events', 'all'); canvas.appendChild(background); origin = document.createElementNS('http://www.w3.org/2000/svg', 'g'); origin.setAttribute('id', 'origin'); canvas.appendChild(origin); } for (const value of this._values.values()) { value.build(); } super.build(document, origin); } async measure() { const document = this.host.document; const window = this.host.window; if (document.fonts && document.fonts.ready) { try { await document.fonts.ready; } catch { // continue regardless of error } } await new Promise((resolve) => { window.requestAnimationFrame(() => { window.requestAnimationFrame(() => { window.requestAnimationFrame(resolve); }); }); }); await super.measure(); } clearSelection() { if (this._selection.size > 0) { for (const element of this._selection) { element.deselect(); } this._selection.clear(); } for (const entry of this._table.values()) { if (entry instanceof grapher.Node) { for (const block of entry.blocks) { if (block.target && block.target.clearSelection) { block.target.clearSelection(); } } } } } select(selection) { if (selection && this.view.target && this.view.target !== this) { this.view.target.clearSelection(); } else { this.clearSelection(); } if (selection) { let array = []; for (const value of selection) { if (this._table.has(value)) { const element = this._table.get(value); array = array.concat(element.select()); this._selection.add(element); } } this.emit('selectionchange'); return array; } this.emit('selectionchange'); return null; } activate(value) { if (this._table.has(value)) { this.select(null); const element = this._table.get(value); element.activate(); return this.select([value]); } return []; } focus(selection) { for (const value of selection) { const element = this._table.get(value); if (element && !this._selection.has(element)) { element.select(); } } } blur(selection) { for (const value of selection) { const element = this._table.get(value); if (element && !this._selection.has(element)) { element.deselect(); } } } restore(state) { const document = this.host.document; const canvas = document.getElementById('canvas'); const origin = document.getElementById('origin'); const background = document.getElementById('background'); const elements = Array.from(canvas.getElementsByClassName('graph-input') || []); if (elements.length === 0) { const nodeElements = Array.from(canvas.getElementsByClassName('graph-node') || []); if (nodeElements.length > 0) { elements.push(nodeElements[0]); } } const size = canvas.getBBox(); const margin = 100; const width = Math.ceil(margin + size.width + margin); const height = Math.ceil(margin + size.height + margin); origin.setAttribute('transform', `translate(${margin - size.x}, ${margin - size.y}) scale(1)`); background.setAttribute('width', width); background.setAttribute('height', height); this._width = width; this._height = height; delete this._scrollLeft; delete this._scrollRight; canvas.setAttribute('viewBox', `0 0 ${width} ${height}`); canvas.setAttribute('width', width); canvas.setAttribute('height', height); this._zoom = state ? state.zoom : 1; this._updateZoom(this._zoom); const container = document.getElementById('target'); const context = state ? this.select([state.context]) : []; if (context.length > 0) { this.scrollTo(context, 'instant'); } else if (elements && elements.length > 0) { // Center view based on input elements const bounds = container.getBoundingClientRect(); const xs = []; const ys = []; for (let i = 0; i < elements.length; i++) { const element = elements[i]; const rect = element.getBoundingClientRect(); const width = Math.min(rect.width, bounds.width); const height = Math.min(rect.height, bounds.height); xs.push(rect.left + (width / 2)); ys.push(rect.top + (height / 2)); } let [x] = xs; const [y] = ys; if (ys.every((y) => y === ys[0])) { x = xs.reduce((a, b) => a + b, 0) / xs.length; } const left = (container.scrollLeft + x - bounds.left) - (bounds.width / 2); const top = (container.scrollTop + y - bounds.top) - (bounds.height / 2); container.scrollTo({ left, top, behavior: 'auto' }); } else { const canvasRect = canvas.getBoundingClientRect(); const graphRect = container.getBoundingClientRect(); const left = (container.scrollLeft + (canvasRect.width / 2) - graphRect.left) - (graphRect.width / 2); const top = (container.scrollTop + (canvasRect.height / 2) - graphRect.top) - (graphRect.height / 2); container.scrollTo({ left, top, behavior: 'auto' }); } } register() { if (!this._events) { this._events = {}; this._events.scroll = (e) => this._scrollHandler(e); this._events.wheel = (e) => this._wheelHandler(e); this._events.gesturestart = (e) => this._gestureStartHandler(e); this._events.pointerdown = (e) => this._pointerDownHandler(e); this._events.touchstart = (e) => this._touchStartHandler(e); const document = this.host.document; const element = document.getElementById('target'); element.focus(); element.addEventListener('scroll', this._events.scroll); element.addEventListener('wheel', this._events.wheel, { passive: false }); element.addEventListener('pointerdown', this._events.pointerdown); if (this.host.environment('agent') === 'safari') { element.addEventListener('gesturestart', this._events.gesturestart, false); } else { element.addEventListener('touchstart', this._events.touchstart, { passive: true }); } } } unregister() { if (this._events) { const document = this.host.document; const element = document.getElementById('target'); element.removeEventListener('scroll', this._events.scroll); element.removeEventListener('wheel', this._events.wheel); element.removeEventListener('pointerdown', this._events.pointerdown); element.removeEventListener('gesturestart', this._events.gesturestart); element.removeEventListener('touchstart', this._events.touchstart); delete this._events; } } get zoom() { return this._zoom; } set zoom(value) { this._updateZoom(value); } _updateZoom(zoom, e) { const document = this.host.document; const container = document.getElementById('target'); const canvas = document.getElementById('canvas'); const limit = this.view.options.direction === 'vertical' ? container.clientHeight / this._height : container.clientWidth / this._width; const min = Math.min(Math.max(limit, 0.15), 1); zoom = Math.max(min, Math.min(zoom, 1.4)); const scrollLeft = this._scrollLeft || container.scrollLeft; const scrollTop = this._scrollTop || container.scrollTop; const x = (e ? e.pageX : (container.clientWidth / 2)) + scrollLeft; const y = (e ? e.pageY : (container.clientHeight / 2)) + scrollTop; const width = zoom * this._width; const height = zoom * this._height; canvas.style.width = `${width}px`; canvas.style.height = `${height}px`; this._scrollLeft = Math.max(0, ((x * zoom) / this._zoom) - (x - scrollLeft)); this._scrollTop = Math.max(0, ((y * zoom) / this._zoom) - (y - scrollTop)); container.scrollLeft = this._scrollLeft; container.scrollTop = this._scrollTop; this._zoom = zoom; } _pointerDownHandler(e) { if (e.pointerType === 'touch' || e.buttons !== 1) { return; } // Workaround for Firefox emitting 'pointerdown' event when scrollbar is pressed if (e.originalTarget) { try { /* eslint-disable no-unused-expressions */ e.originalTarget.id; /* eslint-enable no-unused-expressions */ } catch { return; } } const document = this.host.document; const container = document.getElementById('target'); e.target.setPointerCapture(e.pointerId); this._mousePosition = { left: container.scrollLeft, top: container.scrollTop, x: e.clientX, y: e.clientY }; e.target.style.cursor = 'grabbing'; e.preventDefault(); e.stopImmediatePropagation(); const pointerMoveHandler = (e) => { e.preventDefault(); e.stopImmediatePropagation(); if (this._mousePosition) { const dx = e.clientX - this._mousePosition.x; const dy = e.clientY - this._mousePosition.y; this._mousePosition.moved = dx * dx + dy * dy > 0; if (this._mousePosition.moved) { const document = this.host.document; const container = document.getElementById('target'); container.scrollTop = this._mousePosition.top - dy; container.scrollLeft = this._mousePosition.left - dx; } } }; const clickHandler = (e) => { e.stopPropagation(); document.removeEventListener('click', clickHandler, true); }; const pointerUpHandler = (e) => { e.target.releasePointerCapture(e.pointerId); e.target.style.removeProperty('cursor'); container.removeEventListener('pointerup', pointerUpHandler); container.removeEventListener('pointermove', pointerMoveHandler); if (this._mousePosition && this._mousePosition.moved) { e.preventDefault(); e.stopImmediatePropagation(); delete this._mousePosition; document.addEventListener('click', clickHandler, true); } }; container.addEventListener('pointermove', pointerMoveHandler); container.addEventListener('pointerup', pointerUpHandler); } _touchStartHandler(e) { if (e.touches.length === 2) { this._touchPoints = Array.from(e.touches); this._touchZoom = this._zoom; } const touchMoveHandler = (e) => { if (Array.isArray(this._touchPoints) && this._touchPoints.length === 2 && e.touches.length === 2) { const distance = (points) => { const dx = (points[1].clientX - points[0].clientX); const dy = (points[1].clientY - points[0].clientY); return Math.sqrt(dx * dx + dy * dy); }; const d1 = distance(Array.from(e.touches)); const d2 = distance(this._touchPoints); if (d2 !== 0) { const points = this._touchPoints; const e = { pageX: (points[1].pageX + points[0].pageX) / 2, pageY: (points[1].pageY + points[0].pageY) / 2 }; const zoom = d2 === 0 ? d1 : d1 / d2; this._updateZoom(this._touchZoom * zoom, e); } } }; const document = this.host.document; const container = document.getElementById('target'); const touchEndHandler = () => { container.removeEventListener('touchmove', touchMoveHandler, { passive: true }); container.removeEventListener('touchcancel', touchEndHandler, { passive: true }); container.removeEventListener('touchend', touchEndHandler, { passive: true }); delete this._touchPoints; delete this._touchZoom; }; container.addEventListener('touchmove', touchMoveHandler, { passive: true }); container.addEventListener('touchcancel', touchEndHandler, { passive: true }); container.addEventListener('touchend', touchEndHandler, { passive: true }); } _gestureStartHandler(e) { e.preventDefault(); this._gestureZoom = this._zoom; const document = this.host.document; const container = document.getElementById('target'); const gestureChangeHandler = (e) => { e.preventDefault(); this._updateZoom(this._gestureZoom * e.scale, e); }; const gestureEndHandler = (e) => { container.removeEventListener('gesturechange', gestureChangeHandler, false); container.removeEventListener('gestureend', gestureEndHandler, false); e.preventDefault(); if (this._gestureZoom) { this._updateZoom(this._gestureZoom * e.scale, e); delete this._gestureZoom; } }; container.addEventListener('gesturechange', gestureChangeHandler, false); container.addEventListener('gestureend', gestureEndHandler, false); } _scrollHandler(e) { if (this._scrollLeft && e.target.scrollLeft !== Math.floor(this._scrollLeft)) { delete this._scrollLeft; } if (this._scrollTop && e.target.scrollTop !== Math.floor(this._scrollTop)) { delete this._scrollTop; } } _wheelHandler(e) { if (e.shiftKey || e.ctrlKey || this.view.options.mousewheel === 'zoom') { let factor = 1; if (e.deltaMode === 1) { factor = 0.05; } else if (e.deltaMode) { factor = 1; } else { factor = 0.002; } const delta = -e.deltaY * factor * (e.ctrlKey ? 10 : 1); this._updateZoom(this._zoom * Math.pow(2, delta), e); e.preventDefault(); } } scrollTo(selection, behavior) { if (selection && selection.length > 0) { const document = this.host.document; const container = document.getElementById('target'); const rect = container.getBoundingClientRect(); // Exclude scrollbars const cw = container.clientWidth; const ch = container.clientHeight; // Shrink the test rectangle by 10% const bounds = {}; bounds.left = (rect.x + cw / 2) - (cw * 0.45); bounds.width = cw * 0.9; bounds.right = bounds.left + bounds.width; bounds.top = (rect.y + ch / 2) - (ch * 0.45); bounds.height = ch * 0.9; bounds.bottom = bounds.top + bounds.height; let x = 0; let y = 0; let left = Number.POSITIVE_INFINITY; let right = Number.NEGATIVE_INFINITY; let top = Number.POSITIVE_INFINITY; let bottom = Number.NEGATIVE_INFINITY; for (const element of selection) { const rect = element.getBoundingClientRect(); const width = Math.min(rect.width, bounds.width); const height = Math.min(rect.height, bounds.height); x += rect.left + (width / 2); y += rect.top + (height / 2); left = Math.min(left, rect.left); right = Math.max(right, rect.right); top = Math.min(top, rect.top); bottom = Math.max(bottom, rect.bottom); } // No need to scroll if new selection is in the safe area. if (right <= bounds.right && left >= bounds.left && bottom <= bounds.bottom && top >= bounds.top) { return; } // If new selection is completely out of the bounds, scroll to centerize it. if (bottom - top >= bounds.height || right - left >= bounds.width || right < rect.left || left > rect.right || bottom < rect.top || top > rect.bottom) { x /= selection.length; y /= selection.length; const options = {}; options.left = (container.scrollLeft + x - bounds.left) - (bounds.width / 2); options.top = (container.scrollTop + y - bounds.top) - (bounds.height / 2); options.behavior = behavior || 'smooth'; container.scrollTo(options); return; } const options = {}; options.left = 0; options.top = 0; options.behavior = behavior || 'smooth'; // similar to scrollIntoView block: "nearest" const dr = bounds.right - right; const dl = left - bounds.left; const db = bounds.bottom - bottom; const dt = top - bounds.top; if (right - left < bounds.width) { if (dl < 0) { options.left = dl; } else if (dr < 0) { options.left = -dr; } } if (bottom - top < bounds.height) { if (dt < 0) { options.top = dt; } else if (db < 0) { options.top = -db; } } container.scrollBy(options); } } }; view.Node = class extends grapher.Node { constructor(context, value, type) { super(); this.context = context; this.value = value; this.id = `node-${value.name ? `name-${value.name}` : `id-${(context.counter++)}`}`; this._add(value, type); const inputs = value.inputs; if (type !== 'graph' && type !== 'function' && Array.isArray(inputs)) { for (const argument of inputs) { if (!argument.type || argument.type.endsWith('*')) { if (Array.isArray(argument.value) && argument.value.length === 1 && argument.value[0].initializer) { context.createArgument(argument); } else { for (const value of argument.value) { if (value === null) { // null argument } else if (value.name !== '' && !value.initializer) { context.createValue(value).to.push(this); } else if (value.initializer) { context.createValue(value); } } } } else if (Array.isArray(argument.value) && argument.value.some((value) => value && value.constructor && value.constructor.name === 'Value' && typeof value.name === 'string' && value.name !== '' && !value.initializer)) { for (const value of argument.value) { if (value && value.constructor && value.constructor.name === 'Value' && typeof value.name === 'string' && value.name !== '' && !value.initializer) { context.createValue(value).to.push(this); } } } } } } get class() { return 'graph-node'; } get inputs() { return this.value.inputs; } get outputs() { return this.value.outputs; } _add(value, type) { const node = (type === 'graph' || type === 'function') ? { type: value } : value; const options = this.context.options; const header = this.header(); const category = node.type && node.type.category ? node.type.category : ''; if (node.type && typeof node.type.name !== 'string' || !node.type.name.split) { // #416 const error = new view.Error(`Unsupported node type '${JSON.stringify(node.type.name)}'.`); if (this.context.model && this.context.model.identifier) { error.context = this.context.model.identifier; } throw error; } let content = options.names && (node.name || node.identifier) ? (node.name || node.identifier) : node.type.name.split('.').pop(); let tooltip = options.names && (node.name || node.identifier) ? `[${node.type.name}]` : (node.name || node.identifier); if (content.length > 21) { tooltip = options.names ? `${content}` : `[${content}]`; const begin = content.substring(0, 10); const end = content.substring(content.length - 10, content.length); content = `${begin}\u2026${end}`; } const styles = category ? ['node-item-type', `node-item-type-${category.toLowerCase()}`] : ['node-item-type']; const title = header.add(null, styles, content, tooltip); title.on('click', () => { this.context.activate(value); }); if (type === 'graph' && this.context.view.options.blocks) { const expanded = this.context.blocks.has(value); const icon = expanded ? '\u2212' : '+'; const tooltip = expanded ? 'Collapse Graph' : 'Expand Graph'; this.definition = header.add(null, styles, icon, tooltip); this.definition.on('click', () => { const rect = this.definition.element.getBoundingClientRect(); if (this.context.blocks.has(value)) { this.context.blocks.delete(value); } else { this.context.blocks.add(value); } this.context.view.refresh({ value: this.value, rect }); }); } else if (type === 'graph') { this.definition = header.add(null, styles, '\u25CB', 'Show Subgraph'); this.definition.on('click', async () => await this.context.view.pushTarget(value, this.value)); } else if (node.type.type || (Array.isArray(node.type.nodes) && node.type.nodes.length > 0)) { let icon = '\u0192'; let tooltip = 'Show Function Definition'; if (node.type.type === 'weights') { icon = '\u25CF'; tooltip = 'Show Weights'; } this.definition = header.add(null, styles, icon, tooltip); this.definition.on('click', async () => await this.context.view.pushTarget(node.type, this.value)); } let current = null; const list = () => { if (!current) { current = this.list(); current.on('click', () => this.context.activate(node)); } return current; }; let hiddenTensors = false; const objects = []; const attribute = (argument) => { let content = new view.Formatter(argument.value, argument.type).toString(); if (content && content.length > 12) { content = `${content.substring(0, 12)}\u2026`; } const item = list().argument(argument.name, content); item.tooltip = argument.type; if (!content.startsWith('\u3008')) { item.separator = ' = '; } return item; }; const isObject = (node) => { if (node.name || node.identifier || node.description || (Array.isArray(node.inputs) && node.inputs.length > 0) || (Array.isArray(node.outputs) && node.outputs.length > 0) || (Array.isArray(node.attributes) && node.attributes.length > 0) || (Array.isArray(node.blocks) && node.blocks.length > 0) || (Array.isArray(node.chain) && node.chain.length > 0) || (node.type && Array.isArray(node.type.nodes) && node.type.nodes.length > 0)) { return true; } return false; }; const inputs = node.inputs; if (Array.isArray(inputs)) { for (const argument of inputs) { const type = argument.type; if (argument.visible !== false && ((type === 'graph') || (type === 'object' && isObject(argument.value)) || (type === 'object[]' || type === 'function' || type === 'function[]'))) { objects.push(argument); } else if (options.weights && argument.visible !== false && argument.type !== 'attribute' && Array.isArray(argument.value) && argument.value.length === 1 && argument.value[0].initializer) { const item = this.context.createArgument(argument); list().add(item); } else if (options.weights && (argument.visible === false || Array.isArray(argument.value) && argument.value.length > 1) && (!argument.type || argument.type.endsWith('*')) && argument.value.some((value) => value !== null && value.initializer)) { hiddenTensors = true; } else if (options.attributes && argument.visible !== false && argument.type && !argument.type.endsWith('*')) { const item = attribute(argument); list().add(item); } } } if (Array.isArray(node.attributes)) { const attributes = node.attributes.slice(); attributes.sort((a, b) => a.name.toUpperCase().localeCompare(b.name.toUpperCase())); for (const argument of attributes) { const type = argument.type; if (argument.visible !== false && ((type === 'graph') || (type === 'object') || type === 'object[]' || type === 'function' || type === 'function[]')) { objects.push(argument); } else if (options.attributes && argument.visible !== false) { const item = attribute(argument); list().add(item); } } } if (Array.isArray(node.blocks)) { for (const argument of node.blocks) { const type = argument.type; if (argument.visible !== false && ((type === 'graph') || (type === 'object' && isObject(argument.value)) || (type === 'object[]' || type === 'function' || type === 'function[]'))) { objects.push(argument); } } } if (hiddenTensors) { const item = list().argument('\u3008\u2026\u3009', ''); list().add(item); } for (const argument of objects) { const type = argument.type; let content = null; if (type === 'graph' && this.context.blocks.has(argument.value)) { content = this.context.createGraph(argument.value); content.blocks.push(new view.Block(this.context.view, argument.value, this.context.blocks)); content.activate = () => this.context.view.showTargetProperties(argument.value); const item = list().argument(argument.name, content); list().add(item); } else if (type === 'graph' || type === 'function') { content = this.context.createGraph(argument.value, type); content.activate = () => this.context.view.showTargetProperties(argument.value); const item = list().argument(argument.name, content); list().add(item); } else if (type === 'graph[]') { content = argument.value.map((value) => this.context.createGraph(value)); const item = list().argument(argument.name, content); list().add(item); } else { if (argument.type === 'object') { content = this.context.createNode(argument.value); } else if (type === 'function[]' || argument.type === 'object[]') { content = argument.value.map((value) => this.context.createNode(value)); } const item = list().argument(argument.name, content); list().add(item); } } if (Array.isArray(node.chain) && node.chain.length > 0) { for (const innerNode of node.chain) { this.context.createNode(innerNode); this._add(innerNode); } } if (node.inner) { this.context.createNode(node.inner); this._add(node.inner); } } activate() { this.context.view.showNodeProperties(this.value); } edge(to) { this._edges = this._edges || new Map(); if (!this._edges.has(to)) { this._edges.set(to, new view.Edge(this, to)); } return this._edges.get(to); } }; view.Block = class { constructor(viewRef, target, blocks) { this.target = new view.Graph(viewRef, false); if (blocks) { this.target.blocks = blocks; } this.target.add(target); this.x = 0; this.y = 0; } build(document, parent) { this.element = document.createElementNS('http://www.w3.org/2000/svg', 'g'); this.element.setAttribute('class', 'node-block'); parent.appendChild(this.element); if (!this.first) { this.line = document.createElementNS('http://www.w3.org/2000/svg', 'line'); this.line.setAttribute('class', 'node'); parent.appendChild(this.line); } this._background = document.createElementNS('http://www.w3.org/2000/svg', 'path'); this._background.setAttribute('class', 'node-block-background'); this.element.appendChild(this._background); this._origin = document.createElementNS('http://www.w3.org/2000/svg', 'g'); this.element.appendChild(this._origin); for (const value of this.target.values.values()) { value.build(); } this.target.build(document, this._origin); } async measure() { for (const edge of this.target.edges.values()) { if (edge.label.labelElement) { const box = edge.label.labelElement.getBBox(); edge.label.width = box.width; edge.label.height = box.height; } } await this.target.measure(); await this.target.layout(); const padding = 10; this._padding = padding; this.width = (this.target.width || 0) + 2 * padding; this.height = (this.target.height || 0) + 2 * padding; } async layout() { } update() { const offsetX = this._padding - (this.target.originX || 0); const offsetY = this._padding - (this.target.originY || 0); this.element.setAttribute('transform', `translate(0,${this.y})`); this._origin.setAttribute('transform', `translate(${offsetX},${offsetY})`); this._background.setAttribute('d', grapher.Node.roundedRect(0, 0, this.width, this.height, false, false, this.last, this.last)); this.target.update(); if (this.line) { this.line.setAttribute('x1', 0); this.line.setAttribute('x2', this.width); this.line.setAttribute('y1', this.y); this.line.setAttribute('y2', this.y); } } }; view.Input = class extends grapher.Node { constructor(context, value) { super(); this.context = context; this.value = value; view.Input.counter = view.Input.counter || 0; const types = value.value.map((argument) => argument.type || '').join('\n'); let name = value.name || ''; if (name.length > 16) { name = name.split('/').pop(); } const header = this.header(); const title = header.add(null, ['graph-item-input'], name, types); title.on('click', () => this.context.view.showTargetProperties(this.target)); this.id = `input-${name ? `name-${name}` : `id-${(view.Input.counter++)}`}`; } get target() { return this.context.target === this.context.view.activeTarget ? null : this.context.target; } get class() { return 'graph-input'; } get inputs() { return []; } get outputs() { return [this.value]; } activate() { this.context.view.showTargetProperties(this.target); } edge(to) { this._edges = this._edges || new Map(); if (!this._edges.has(to)) { this._edges.set(to, new view.Edge(this, to)); } return this._edges.get(to); } }; view.Output = class extends grapher.Node { constructor(context, value) { super(); this.context = context; this.value = value; if (Array.isArray(value.value)) { const types = value.value.map((argument) => argument.type || '').join('\n'); let name = value.name || ''; if (name.length > 16) { name = name.split('/').pop(); } const header = this.header(); const title = header.add(null, ['graph-item-output'], name, types); title.on('click', () => this.context.view.showTargetProperties(this.target)); } } get target() { return this.context.target === this.context.view.activeTarget ? null : this.context.target; } get inputs() { return [this.value]; } get outputs() { return []; } activate() { this.context.view.showTargetProperties(this.target); } }; view.Value = class { constructor(context, value) { this.context = context; this.value = value; this.from = null; this.to = []; } controlDependency(node) { this._controlDependencies = this._controlDependencies || new Set(); this._controlDependencies.add(this.to.length); this.to.push(node); } build() { this._edges = this._edges || []; if (this.from && Array.isArray(this.to)) { for (let i = 0; i < this.to.length; i++) { const to = this.to[i]; let content = ''; const type = this.value.type; if (type && type.shape && type.shape.dimensions && type.shape.dimensions.length > 0 && type.shape.dimensions.every((dim) => !dim || Number.isInteger(dim) || typeof dim === 'bigint' || (typeof dim === 'string'))) { content = type.shape.dimensions.map((dim) => (dim !== null && dim !== undefined && dim !== -1 && dim !== -1n) ? dim : '?').join('\u00D7'); content = content.length > 16 ? '' : content; } if (this.context.options.names) { content = this.value.name.split('\n').shift(); // custom argument id } const edge = this.from.edge(to); if (!edge.value) { edge.value = this; if (content) { edge.label = content; } edge.id = `edge-${this.value.name}`; if (this._controlDependencies && this._controlDependencies.has(i)) { edge.class = 'edge-path-control-dependency'; } } this.context.setEdge(edge); this._edges.push(edge); } } } select() { let array = []; if (Array.isArray(this._edges)) { for (const edge of this._edges) { array = array.concat(edge.select()); } } return array; } deselect() { if (Array.isArray(this._edges)) { for (const edge of this._edges) { edge.deselect(); } } } activate() { if (this.value && this.from && Array.isArray(this.to) && !this.value.initializer) { const from = this.from.value; const to = this.to.map((node) => node.value); this.context.view.showConnectionProperties(this.value, from, to); } else if (this.value && this.value.initializer) { this.context.view.showTensorProperties({ value: [this.value] }); } } }; view.Argument = class extends grapher.Argument { constructor(context, value) { const name = value.name; let content = ''; let separator = ''; let tooltip = ''; if (Array.isArray(value.value) && value.value.length === 1 && value.value[0].initializer) { const tensor = value.value[0].initializer; const type = value.value[0].type; tooltip = type.toString(); content = view.Formatter.tensor(tensor); if (!content.startsWith('\u3008')) { separator = ' = '; } } super(name, content); this.context = context; this.value = value; this.separator = separator; this.tooltip = tooltip; } focus() { this.context.focus([this.value]); } blur() { this.context.blur([this.value]); } activate() { this.context.view.showTensorProperties(this.value); } }; view.Edge = class extends grapher.Edge { constructor(from, to) { super(from, to); this.v = from.name; this.w = to.name; } get minlen() { if (this.from.inputs.every((argument) => (!argument.type || argument.type.endsWith('*')) && argument.value.every((value) => value.initializer))) { return 2; } return 1; } focus() { this.value.context.focus([this.value.value]); } blur() { this.value.context.blur([this.value.value]); } activate() { this.value.context.activate(this.value.value); } }; view.Sidebar = class { constructor(host) { this._host = host; this._stack = []; const pop = () => this._update(this._stack.slice(0, -1)); this._closeSidebarHandler = () => pop(); this._closeSidebarKeyDownHandler = (e) => { if (e.keyCode === 27) { e.stopPropagation(); e.preventDefault(); pop(); } }; const sidebar = this._element('sidebar'); sidebar.addEventListener('transitionend', (event) => { if (event.propertyName === 'opacity' && sidebar.style.opacity === '0') { const content = this._element('sidebar-content'); content.replaceChildren(); } }); } _element(id) { return this._host.document.getElementById(id); } open(content, title) { const element = this._render(content); const entry = { title, element, content }; this._update([entry]); } close() { this._update([]); } push(content, title) { const element = this._render(content); const entry = { title, content, element }; this._update(this._stack.concat(entry)); } get identifier() { if (this._stack.length > 0) { const content = this._stack[this._stack.length - 1].content; if (content.identifier) { return content.identifier; } } return ''; } _render(content) { try { content.render(); } catch (error) { content.error(error, false); } const element = content.element; return Array.isArray(element) ? element : [element]; } _update(stack) { const sidebar = this._element('sidebar'); const element = this._element('sidebar-content'); const container = this._element('target'); const closeButton = this._element('sidebar-closebutton'); closeButton.removeEventListener('click', this._closeSidebarHandler); this._host.document.removeEventListener('keydown', this._closeSidebarKeyDownHandler); if (this._stack.length > 0) { const entry = this._stack.pop(); const content = entry.content; if (content && content.deactivate) { content.deactivate(); } } if (stack) { this._stack = stack; } if (this._stack.length > 0) { const entry = this._stack[this._stack.length - 1]; this._element('sidebar-title').innerHTML = entry.title || ''; closeButton.addEventListener('click', this._closeSidebarHandler); if (typeof entry.content === 'string') { element.innerHTML = entry.element; } else if (entry.element instanceof Array) { element.replaceChildren(...entry.element); } else { element.replaceChildren(entry.element); } sidebar.style.width = 'min(calc(100% * 0.6), 42em)'; sidebar.style.right = 0; sidebar.style.opacity = 1; this._host.document.addEventListener('keydown', this._closeSidebarKeyDownHandler); container.style.width = 'max(40vw, calc(100vw - 42em))'; const content = entry.content; if (content && content.activate) { content.activate(); } } else { sidebar.style.right = 'calc(0px - min(calc(100% * 0.6), 42em))'; sidebar.style.opacity = 0; const clone = element.cloneNode(true); element.parentNode.replaceChild(clone, element); container.style.width = '100%'; container.focus(); } } }; view.Control = class { constructor(context) { this._view = context; this._host = context.host; } createElement(tagName, className) { const element = this._host.document.createElement(tagName); if (className) { element.setAttribute('class', className); } return element; } createTextNode(data) { const node = this._host.document.createTextNode(data); return node; } on(event, callback) { this._events = this._events || {}; this._events[event] = this._events[event] || []; this._events[event].push(callback); } emit(event, data) { try { if (this._events && this._events[event]) { for (const callback of this._events[event]) { callback(this, data); } } } catch (error) { this.error(error, false); } } error(error, fatal) { this._view.exception(error, fatal || false); } escape(value) { return value.toString().split('&').join('&').split('<').join('<').split('>').join('>'); } }; view.Expander = class extends view.Control { constructor(context) { super(context); this.element = this.createElement('div', 'sidebar-item-value'); this._count = -1; } render() { return [this.element]; } expandable() { if (!this._expander) { this._expander = this.createElement('div', 'sidebar-item-value-expander'); this._expander.innerText = '+'; this._expander.addEventListener('click', () => this.toggle()); this.add(this._expander); } } add(element) { this.element.appendChild(element); } control(element) { this.add(element); } toggle() { this._count = this._count === -1 ? this.element.childElementCount : this._count; if (this._expander) { while (this.element.childElementCount > this._count) { this.element.removeChild(this.element.lastChild); } if (this._expander.innerText === '+') { this._expander.innerText = '-'; this.expand(); } else { this._expander.innerText = '+'; this.collapse(); } } } expand() { } collapse() { } }; view.TargetSelector = class extends view.Control { constructor(context, element) { super(context); this._element = element; [this._select] = element.getElementsByTagName('select'); this._select.addEventListener('change', (e) => { const target = this._targets[e.target.selectedIndex]; this.emit('change', target); }); this._targets = []; } update(model, stack) { while (this._select.firstChild) { this._select.removeChild(this._select.firstChild); } this._targets = []; const current = stack.length > 0 ? stack[stack.length - 1] : null; const section = (title, targets) => { if (targets.length > 0) { const group = this.createElement('optgroup'); group.setAttribute('label', title); this._select.appendChild(group); for (let i = 0; i < targets.length; i++) { const target = targets[i]; const option = this.createElement('option'); option.innerText = target.name; group.appendChild(option); if (current && current.target === target.target && current.signature === target.signature) { option.setAttribute('selected', 'true'); this._select.setAttribute('title', target.name); } this._targets.push(target); } } }; const modules = []; const signatures = []; const functions = []; if (model && Array.isArray(model.modules)) { for (const graph of model.modules) { const name = graph.name || '-'; modules.push({ name, target: graph, signature: null }); if (Array.isArray(graph.functions)) { for (const func of graph.functions) { functions.push({ name: `${name}.${func.name}`, target: func, signature: null }); } } if (Array.isArray(graph.signatures)) { for (const signature of graph.signatures) { signatures.push({ name: `${name}.${signature.name}`, target: graph, signature }); } } } } if (model && Array.isArray(model.functions)) { for (const func of model.functions) { functions.push({ name: func.name, target: func, signature: null }); } } section('Modules', modules); section('Signatures', signatures); section('Functions', functions); const visible = functions.length > 0 || signatures.length > 0 || modules.length > 1; this._element.style.display = visible ? 'inline' : 'none'; } }; view.ObjectSidebar = class extends view.Control { constructor(context) { super(context); this.element = this.createElement('div', 'sidebar-object'); } addSection(title) { const element = this.createElement('div', 'sidebar-section'); element.innerText = title; this.element.appendChild(element); } addEntry(name, item) { const entry = new view.NameValueView(this._view, name, item); const element = entry.render(); this.element.appendChild(element); } addProperty(name, value, style) { const item = new view.TextView(this._view, value, style); this.addEntry(name, item); return item; } addArgument(name, argument, source) { const value = new view.ArgumentView(this._view, argument, source); value.on('focus', (sender, value) => { this.emit('focus', value); this._focused = this._focused || new Set(); this._focused.add(value); }); value.on('blur', (sender, value) => { this.emit('blur', value); this._focused = this._focused || new Set(); this._focused.delete(value); }); value.on('select', (sender, value) => this.emit('select', value)); value.on('activate', (sender, value) => this.emit('activate', value)); value.on('deactivate', (sender, value) => this.emit('deactivate', value)); this.addEntry(name, value); return value; } error(error, fatal) { super.error(error, fatal); const element = this.createElement('span'); const title = this.createElement('b'); title.textContent = 'ERROR: '; element.appendChild(title); const message = this.createTextNode(` ${error.message}`); element.appendChild(message); this.element.appendChild(element); } }; view.NodeSidebar = class extends view.ObjectSidebar { constructor(context, node) { super(context); this._node = node; } get identifier() { return 'node'; } render() { const node = this._node; if (node.type) { const type = node.type; const item = this.addProperty('type', node.type.identifier || node.type.name); if (type && (type.description || type.inputs || type.outputs || type.attributes)) { let icon = '?'; let tooltip = 'Show Definition'; if (type.type === 'weights') { icon = '\u25CF'; tooltip = 'Show Weights'; } else if (Array.isArray(type.nodes)) { icon = '\u0192'; } item.action(icon, tooltip, () => { this.emit('show-definition', null); }); } const module = node.type.module; const version = node.type.version; const status = node.type.status; if (module || version || status) { const list = [module, version ? `v${version}` : '', status]; const value = list.filter((value) => value).join(' '); this.addProperty('module', value, 'nowrap'); } } if (node.name) { this.addProperty('name', node.name, 'nowrap'); } if (node.identifier) { this.addProperty('identifier', node.identifier, 'nowrap'); } if (node.description) { this.addProperty('description', node.description); } if (node.device) { this.addProperty('device', node.device); } const attributes = node.attributes; if (Array.isArray(attributes) && attributes.length > 0) { this.addSection('Attributes'); attributes.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })); for (const attribute of attributes) { this.addArgument(attribute.name, attribute, 'attribute'); } } const inputs = node.inputs; if (Array.isArray(inputs) && inputs.length > 0) { this.addSection('Inputs'); for (const input of inputs) { const name = input.name; this.addArgument(name, input); } } const outputs = node.outputs; if (Array.isArray(outputs) && outputs.length > 0) { this.addSection('Outputs'); for (const output of outputs) { const name = output.name; this.addArgument(name, output); } } const blocks = node.blocks; if (Array.isArray(blocks) && blocks.length > 0) { this.addSection('Blocks'); for (const block of blocks) { const name = block.name; this.addArgument(name, block); } } const metadata = this._view.model.attachment.metadata.node(node); if (Array.isArray(metadata) && metadata.length > 0) { this.addSection('Metadata'); for (const argument of metadata) { this.addArgument(argument.name, argument, 'attribute'); } } const metrics = this._view.model.attachment.metrics.node(node); if (Array.isArray(metrics) && metrics.length > 0) { this.addSection('Metrics'); for (const argument of metrics) { this.addArgument(argument.name, argument, 'attribute'); } } } activate() { this.emit('select', this._node); } deactivate() { this.emit('select', null); if (this._focused) { for (const value of this._focused) { this.emit('blur', value); } this._focused.clear(); } } }; view.NameValueView = class extends view.Control { constructor(context, name, value) { super(context); this._name = name; this._value = value; const nameElement = this.createElement('div', 'sidebar-item-name'); const input = this.createElement('input'); input.setAttribute('type', 'text'); input.setAttribute('value', name); input.setAttribute('title', name); input.setAttribute('readonly', 'true'); nameElement.appendChild(input); const valueElement = this.createElement('div', 'sidebar-item-value-list'); for (const element of value.render()) { valueElement.appendChild(element); } this.element = this.createElement('div', 'sidebar-item'); this.element.appendChild(nameElement); this.element.appendChild(valueElement); } get name() { return this._name; } render() { return this.element; } toggle() { this._value.toggle(); } }; view.TextView = class extends view.Control { constructor(context, value, style) { super(context); this.element = this.createElement('div', 'sidebar-item-value'); let className = 'sidebar-item-value-line'; if (value !== null && value !== undefined) { const list = Array.isArray(value) ? value : [value]; for (const item of list) { const line = this.createElement('div', className); switch (style) { case 'code': { const element = this.createElement('code'); element.textContent = item; line.appendChild(element); break; } case 'bold': { const element = this.createElement('b'); element.textContent = item; line.appendChild(element); break; } case 'nowrap': { line.innerText = item; line.style.whiteSpace = style; break; } default: { line.innerText = item; break; } } this.element.appendChild(line); className = 'sidebar-item-value-line-border'; } } else { const line = this.createElement('div', className); line.classList.add('sidebar-item-disable-select'); line.innerHTML = ' '; this.element.appendChild(line); } } action(text, description, callback) { const action = this.createElement('div', 'sidebar-item-value-expander'); action.setAttribute('title', description); action.addEventListener('click', () => callback()); action.innerHTML = text; this.element.insertBefore(action, this.element.childNodes[0]); } render() { return [this.element]; } toggle() { } }; view.ArgumentView = class extends view.Control { constructor(context, argument, source) { super(context); this._argument = argument; this._source = source; this._elements = []; this._items = []; const type = argument.type === 'attribute' ? null : argument.type; let value = argument.value; if (argument.type === 'attribute') { this._source = 'attribute'; } if (argument.type === 'tensor' || argument.type === 'tensor?') { if (value === null || (value && value.constructor && value.constructor.name === 'Value')) { value = [value]; } else { value = [{ type: value.type, initializer: value }]; } } else if (argument.type === 'tensor[]' || argument.type === 'tensor?[]') { value = value.map((value) => { if (value === null || (value && value.constructor && value.constructor.name === 'Value')) { return value; } return { type: value.type, initializer: value }; }); } this._source = typeof type === 'string' && !type.endsWith('*') ? 'attribute' : this._source; const primitive = typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint'; if (primitive) { const item = new view.PrimitiveView(context, argument); this._items.push(item); } else if (this._source === 'attribute' && type !== 'tensor' && type !== 'tensor?' && type !== 'tensor[]' && type !== 'tensor?[]') { this._source = 'attribute'; const item = new view.PrimitiveView(context, argument); this._items.push(item); } else if (Array.isArray(value) && value.length === 0) { const item = new view.TextView(this._view, null); this._items.push(item); } else { const values = value; for (const value of values) { const emit = values.length === 1 && value && value.initializer; const target = emit ? argument : value; if (value === null) { const item = new view.TextView(this._view, null); this._items.push(item); } else { const item = new view.ValueView(context, value, this._source); item.on('focus', () => this.emit('focus', target)); item.on('blur', () => this.emit('blur', target)); item.on('activate', () => this.emit('activate', target)); item.on('select', () => this.emit('select', target)); this._items.push(item); } } } for (const item of this._items) { this._elements.push(...item.render()); } } render() { return this._elements; } toggle() { for (const item of this._items) { item.toggle(); } } }; view.PrimitiveView = class extends view.Expander { constructor(context, argument) { super(context); try { this._argument = argument; const type = argument.type === 'attribute' ? null : argument.type; const value = argument.value; if (type) { this.expandable(); } switch (type) { case 'graph': { const line = this.createElement('div', 'sidebar-item-value-line-link'); line.textContent = value.name || '\u00A0'; line.addEventListener('click', () => this.emit('activate', value)); this.add(line); break; } case 'function': { const line = this.createElement('div', 'sidebar-item-value-line-link'); line.textContent = value.name; line.addEventListener('click', () => this.emit('activate', value)); this.add(line); break; } case 'object[]': { for (const obj of argument.value) { const line = this.createElement('div', 'sidebar-item-value-line'); line.textContent = obj.type ? obj.type.name : '?'; this.add(line); } break; } default: { const formatter = new view.Formatter(value, type); let content = formatter.toString(); if (content) { if (content.length > 2000) { content = `${content.substring(0, 2000)}\u2026`; } const multiline = content.includes('\n'); if (!multiline && content.length > 80) { this.expandable(); } content = this.escape(content); if (multiline) { content = content.split('\n').join('${content}`;
this.add(line);
}
const description = this._argument.description;
if (description) {
const line = this.createElement('div', 'sidebar-item-value-line-border');
line.innerHTML = description;
this.add(line);
}
} catch (error) {
super.error(error, false);
this._info('ERROR', error.message);
}
}
collapse() {
if (this._line) {
this._line.classList.remove('sidebar-item-value-line-wrap');
}
}
_info(name, value) {
const line = this.createElement('div');
line.innerHTML = `${name}: ${this.escape(value)}`;
this._add(line);
}
_add(child) {
child.className = this._first === false ? 'sidebar-item-value-line-border' : 'sidebar-item-value-line';
this.add(child);
this._first = false;
}
};
view.ValueView = class extends view.Expander {
constructor(context, value, source) {
super(context);
this._value = value;
try {
if (value && value.constructor && value.constructor.name === 'Value' && source === 'attribute') {
source = '';
}
const type = this._value.type;
const initializer = this._value.initializer;
const quantization = this._value.quantization;
const location = this._value.location !== undefined;
if (initializer) {
this.element.classList.add('sidebar-item-value-content');
}
if (type || initializer || quantization || location || source === 'attribute') {
this.expandable();
}
if (initializer && source !== 'attribute') {
const element = this.createElement('div', 'sidebar-item-value-button');
element.classList.add('sidebar-item-value-button-tool');
element.setAttribute('title', 'Show Tensor');
element.innerHTML = ``;
element.addEventListener('pointerenter', () => this.emit('focus', this._value));
element.addEventListener('pointerleave', () => this.emit('blur', this._value));
element.style.cursor = 'pointer';
element.addEventListener('click', () => this.emit('activate', this._value));
this.control(element);
}
const name = this._value.name ? this._value.name.split('\n').shift() : ''; // custom argument id
this._hasId = name && source !== 'attribute' ? true : false;
this._hasCategory = initializer && initializer.category && source !== 'attribute' ? true : false;
if (this._hasId || (!this._hasCategory && !type && source !== 'attribute')) {
this._hasId = true;
const element = this.createElement('div', 'sidebar-item-value-line');
if (typeof name !== 'string') {
throw new Error(`Invalid value identifier '${JSON.stringify(name)}'.`);
}
const text = this.createElement('b');
text.innerText = name || ' ';
const line = this.createElement('span', 'sidebar-item-value-line-content');
line.innerText = 'name: ';
line.appendChild(text);
element.appendChild(line);
element.addEventListener('pointerenter', () => this.emit('focus', this._value));
element.addEventListener('pointerleave', () => this.emit('blur', this._value));
element.style.cursor = 'pointer';
element.addEventListener('click', () => this.emit('activate', this._value));
this._add(element);
} else if (this._hasCategory) {
this._bold('category', initializer.category);
} else if (type) {
this._code('tensor', type);
}
} catch (error) {
super.error(error, false);
this._info('ERROR', error.message);
}
}
render() {
return [this.element];
}
expand() {
try {
const initializer = this._value.initializer;
if (this._hasId && this._hasCategory) {
this._bold('category', initializer.category);
}
let type = null;
let denotation = null;
if (this._value.type) {
type = this._value.type.toString();
denotation = this._value.type.denotation || null;
}
if (type && (this._hasId || this._hasCategory)) {
this._code('tensor', type);
}
if (denotation) {
this._code('denotation', denotation);
}
const description = this._value.description;
if (description) {
const line = this.createElement('div', 'sidebar-item-value-line-border');
line.innerHTML = description;
this.add(line);
}
const identifier = this._value.identifier;
if (identifier !== undefined) {
this._bold('identifier', identifier);
}
const layout = this._value.type ? this._value.type.layout : null;
if (layout) {
this._bold('layout', layout.replace('.', ' '));
}
const quantization = this._value.quantization;
if (quantization) {
if (typeof quantization.type !== 'string') {
throw new view.Error('Unsupported quantization value.');
}
const value = new view.Quantization(quantization).toString();
if (quantization.type && (quantization.type !== 'linear' || (value && value !== 'q'))) {
const line = this.createElement('div', 'sidebar-item-value-line-border');
const content = [
``
];
if (value) {
content.push(`${value}`);
}
line.innerHTML = content.join('');
this._add(line);
}
}
if (initializer) {
if (initializer.location) {
this._bold('location', initializer.location);
}
const stride = initializer.stride;
if (Array.isArray(stride) && stride.length > 0) {
this._code('stride', stride.join(','));
}
const tensor = new view.TensorView(this._view, initializer);
const content = tensor.content;
const line = this.createElement('div', 'sidebar-item-value-line-border');
line.appendChild(content);
this._add(line);
}
} catch (error) {
super.error(error, false);
this._info('ERROR', error.message);
}
}
_bold(name, value) {
const line = this.createElement('div');
line.innerHTML = `${name}: ${this.escape(value)}`;
this._add(line);
}
_code(name, value) {
const line = this.createElement('div');
line.innerHTML = `${name}: ${this.escape(value)}`;
this._add(line);
}
_info(name, value) {
const line = this.createElement('div');
line.innerHTML = `${name}: ${this.escape(value)}`;
this._add(line);
}
_add(child) {
child.className = this._first === false ? 'sidebar-item-value-line-border' : 'sidebar-item-value-line';
this.add(child);
this._first = false;
}
};
view.TensorView = class extends view.Expander {
constructor(context, value, tensor) {
super(context);
this._value = value;
this._tensor = tensor || new base.Tensor(value);
}
render() {
if (!this._button) {
this.expandable();
this._button = this.createElement('div', 'sidebar-item-value-button');
this._button.setAttribute('style', 'float: left;');
this._button.innerHTML = ``;
this._button.addEventListener('click', () => this.toggle());
this.control(this._button);
const line = this.createElement('div', 'sidebar-item-value-line');
line.classList.add('sidebar-item-disable-select');
line.innerHTML = ' ';
this.element.appendChild(line);
}
return super.render();
}
expand() {
try {
const content = this.content;
const container = this.createElement('div', 'sidebar-item-value-line-border');
container.appendChild(content);
this.element.appendChild(container);
} catch (error) {
this.error(error, false);
}
}
get content() {
const content = this.createElement('pre');
const value = this._value;
const tensor = this._tensor;
if (tensor.encoding !== '<' && tensor.encoding !== '>' && tensor.encoding !== '|') {
content.innerHTML = `Tensor encoding '${tensor.layout}' is not implemented.`;
} else if (tensor.layout && (tensor.layout !== 'sparse' && tensor.layout !== 'sparse.coo')) {
content.innerHTML = `Tensor layout '${tensor.layout}' is not implemented.`;
} else if (tensor.type && tensor.type.dataType === '?') {
content.innerHTML = 'Tensor data type is not defined.';
} else if (tensor.type && !tensor.type.shape) {
content.innerHTML = 'Tensor shape is not defined.';
} else {
content.innerHTML = '⏳';
const promise = value.peek && !value.peek() ? value.read() : Promise.resolve();
promise.then(() => {
if (tensor.empty) {
content.innerHTML = 'Tensor data is empty.';
} else {
content.innerHTML = tensor.toString();
if (this._host.save && value.type.shape && value.type.shape.dimensions && value.type.shape.dimensions.length > 0) {
this._saveButton = this.createElement('div', 'sidebar-item-value-button');
this._saveButton.classList.add('sidebar-item-value-button-context');
this._saveButton.setAttribute('style', 'float: right;');
this._saveButton.innerHTML = '💾';
this._saveButton.addEventListener('click', async () => {
await this.export();
});
content.insertBefore(this._saveButton, content.firstChild);
}
}
}).catch((error) => {
content.innerHTML = error.message;
});
}
return content;
}
error(error, fatal) {
super.error(error, fatal);
const element = this.createElement('div', 'sidebar-item-value-line');
const title = this.createElement('b');
title.textContent = 'ERROR: ';
element.appendChild(title);
const message = this.createTextNode(error.message);
element.appendChild(message);
this.element.appendChild(element);
}
async export() {
const window = this._host.window;
const tensor = this._tensor;
const defaultPath = tensor.name ? tensor.name.split('/').join('_').split(':').join('_').split('.').join('_') : 'tensor';
const file = await this._host.save('NumPy Array', 'npy', defaultPath);
if (file) {
try {
let data_type = '?';
switch (tensor.type.dataType) {
case 'boolean': data_type = 'bool'; break;
case 'bfloat16': data_type = 'float32'; break;
case 'float4e2m1fn': data_type = 'float16'; break;
case 'float6e2m3fn': data_type = 'float16'; break;
case 'float6e3m2fn': data_type = 'float16'; break;
case 'float8e3m4': data_type = 'float16'; break;
case 'float8e4m3': data_type = 'float16'; break;
case 'float8e4m3b11fnuz': data_type = 'float16'; break;
case 'float8e4m3fn': data_type = 'float16'; break;
case 'float8e4m3fnuz': data_type = 'float16'; break;
case 'float8e5m2': data_type = 'float16'; break;
case 'float8e5m2fnuz': data_type = 'float16'; break;
case 'float8e8m0fnu': data_type = 'float16'; break;
case 'int4': data_type = 'int8'; break;
case 'int48': data_type = 'int64'; break;
default: data_type = tensor.type.dataType; break;
}
const python = await import('./python.js');
const execution = new python.Execution();
const io = execution.__import__('io');
const numpy = execution.register('numpy');
const bytes = new io.BytesIO();
const dtype = new numpy.dtype(data_type);
const array = numpy.asarray(tensor.value, dtype);
numpy.save(bytes, array);
bytes.seek(0);
const blob = new window.Blob([bytes.read()], { type: 'application/octet-stream' });
await this._host.export(file, blob);
} catch (error) {
this._view.error(error, 'Error saving NumPy tensor.', null);
}
}
}
};
view.NodeView = class extends view.Expander {
constructor(context, node) {
super(context);
this._node = node;
const name = node.name;
const type = node.type ? node.type.name : '';
if (name && type) {
this.expandable();
}
if (type) {
const type = node.type.name;
const element = this.createElement('div', 'sidebar-item-value-line');
element.innerHTML = ``;
element.addEventListener('pointerenter', () => this.emit('focus', this._node));
element.addEventListener('pointerleave', () => this.emit('blur', this._node));
element.addEventListener('click', () => this.emit('activate', this._node));
element.style.cursor = 'pointer';
this.element.appendChild(element);
} else {
const element = this.createElement('div', 'sidebar-item-value-line');
element.innerHTML = ``;
element.addEventListener('pointerenter', () => this.emit('focus', this._node));
element.addEventListener('pointerleave', () => this.emit('blur', this._node));
element.addEventListener('click', () => this.emit('activate', this._node));
element.style.cursor = 'pointer';
this.element.appendChild(element);
}
}
expand() {
const name = this._node.name;
const element = this.createElement('div', 'sidebar-item-value-line-border');
element.innerHTML = ``;
element.addEventListener('pointerenter', () => this.emit('focus', this._node));
element.addEventListener('pointerleave', () => this.emit('blur', this._node));
element.addEventListener('click', () => this.emit('activate', this._node));
element.style.cursor = 'pointer';
this.element.appendChild(element);
}
};
view.NodeListView = class extends view.Control {
constructor(context, list) {
super(context);
this._elements = [];
for (const node of list) {
const item = new view.NodeView(this._view, node);
item.on('focus', (sender, value) => this.emit('focus', value));
item.on('blur', (sender, value) => this.emit('blur', value));
item.on('activate', (sender, value) => this.emit('activate', value));
item.on('deactivate', (sender, value) => this.emit('deactivate', value));
item.on('select', (sender, value) => this.emit('select', value));
item.toggle();
for (const element of item.render()) {
this._elements.push(element);
}
}
}
render() {
return this._elements;
}
};
view.ConnectionSidebar = class extends view.ObjectSidebar {
constructor(context, value, from, to) {
super(context);
this._value = value;
this._from = from;
this._to = to;
}
get identifier() {
return 'connection';
}
render() {
const value = this._value;
const from = this._from;
const to = this._to;
const [name] = value.name.split('\n');
this.addProperty('name', name);
if (value.type) {
const item = new view.ValueView(this._view, value);
this.addEntry('type', item);
item.toggle();
}
if (from) {
this.addSection('Inputs');
this.addNodeList('from', [from]);
}
if (Array.isArray(to) && to.length > 0) {
this.addSection('Outputs');
this.addNodeList('to', to);
}
const metadata = this._view.model.attachment.metadata.value(value);
if (Array.isArray(metadata) && metadata.length > 0) {
this.addSection('Metadata');
for (const argument of metadata) {
this.addArgument(argument.name, argument, 'attribute');
}
}
const metrics = this._view.model.attachment.metrics.value(value);
if (Array.isArray(metrics) && metrics.length > 0) {
this.addSection('Metrics');
for (const argument of metrics) {
this.addArgument(argument.name, argument, 'attribute');
}
}
}
addNodeList(name, list) {
const entry = new view.NodeListView(this._view, list);
entry.on('focus', (sender, value) => {
this.emit('focus', value);
this._focused = this._focused || new Set();
this._focused.add(value);
});
entry.on('blur', (sender, value) => {
this.emit('blur', value);
this._focused = this._focused || new Set();
this._focused.delete(value);
});
entry.on('select', (sender, value) => this.emit('select', value));
entry.on('activate', (sender, value) => this.emit('activate', value));
this.addEntry(name, entry);
}
activate() {
this.emit('select', this._value);
}
deactivate() {
this.emit('select', null);
if (this._focused) {
for (const value of this._focused) {
this.emit('blur', value);
}
this._focused.clear();
}
}
};
view.TensorSidebar = class extends view.ObjectSidebar {
constructor(context, value) {
super(context);
this._value = value;
}
get identifier() {
return 'tensor';
}
render() {
const [value] = this._value.value;
const tensor = value.initializer;
const name = tensor && tensor.name ? tensor.name : value.name.split('\n')[0];
if (name) {
this.addProperty('name', name);
}
if (tensor) {
const category = tensor.category;
if (category) {
this.addProperty('category', category);
}
const description = tensor.description;
if (description) {
this.addProperty('description', description);
}
const type = tensor.type;
if (type) {
const dataType = type.dataType;
this.addProperty('type', `${dataType}`, 'code');
const shape = type.shape && Array.isArray(type.shape.dimensions) ? type.shape.dimensions.toString(', ') : '?';
if (shape) {
this.addProperty('shape', shape, 'code');
}
const denotation = type.denotation;
if (denotation) {
this.addProperty('denotation', denotation, 'code');
}
const layout = type.layout;
if (layout) {
this.addProperty('layout', layout.replace('.', ' '));
}
}
const location = tensor.location;
if (location) {
this.addProperty('location', tensor.location);
}
const stride = tensor.stride;
if (Array.isArray(stride) && stride.length > 0) {
this.addProperty('stride', stride.join(','), 'code');
}
const value = new view.TensorView(this._view, tensor, this._tensor);
this.addEntry('value', value);
const attributes = tensor.attributes;
if (Array.isArray(attributes) && attributes.length > 0) {
this.addSection('Attributes');
for (const attribute of attributes) {
this.addArgument(attribute.name, attribute, 'attribute');
}
}
const metadata = this._view.model.attachment.metadata.tensor(tensor);
if (Array.isArray(metadata) && metadata.length > 0) {
this.addSection('Metadata');
for (const argument of metadata) {
this.addArgument(argument.name, argument, 'attribute');
}
}
}
// Metrics
if (value.initializer) {
const tensor = value.initializer;
const promise = tensor.peek && !tensor.peek() ? tensor.read() : Promise.resolve();
promise.then(() => {
this._tensor = new base.Tensor(tensor);
if (!this._tensor.empty) {
if (!this._metrics) {
const tensor = new metrics.Tensor(this._tensor);
this._metrics = this._view.model.attachment.metrics.tensor(tensor);
}
if (this._metrics.length > 0) {
this.addSection('Metrics');
for (const metric of this._metrics) {
const value = metric.type === 'percentage' ? `${(metric.value * 100).toFixed(1)}%` : metric.value;
const argument = new metadata.Argument(metric.name, value, metric.type);
this.addArgument(metric.name, argument, 'attribute');
}
}
}
});
}
}
activate() {
this.emit('select', this._value);
}
deactivate() {
this.emit('select', null);
}
};
view.ModelSidebar = class extends view.ObjectSidebar {
constructor(context, model) {
super(context);
this._model = model;
}
get identifier() {
return 'model';
}
render() {
const model = this._model;
if (model.format) {
this.addProperty('format', model.format);
}
if (model.producer) {
this.addProperty('producer', model.producer);
}
if (model.name) {
this.addProperty('name', model.name);
}
if (model.version) {
this.addProperty('version', model.version);
}
if (model.description) {
this.addProperty('description', model.description);
}
if (model.domain) {
this.addProperty('domain', model.domain);
}
if (model.imports) {
this.addProperty('imports', model.imports);
}
if (model.runtime) {
this.addProperty('runtime', model.runtime);
}
if (model.source) {
this.addProperty('source', model.source);
}
const metadata = this._view.model.attachment.metadata.model(model);
if (Array.isArray(metadata) && metadata.length > 0) {
this.addSection('Metadata');
for (const argument of metadata) {
this.addArgument(argument.name, argument, 'attribute');
}
}
const metrics = this.metrics;
if (Array.isArray(metrics) && metrics.length > 0) {
this.addSection('Metrics');
for (const argument of metrics) {
this.addArgument(argument.name, argument, 'attribute');
}
}
}
get metrics() {
const model = new metrics.Model(this._model);
return this._view.model.attachment.metrics.model(model);
}
};
view.TargetSidebar = class extends view.ObjectSidebar {
constructor(context, target, signature) {
super(context);
this._target = target;
this._signature = signature;
}
render() {
const target = this._target;
const signature = this._signature;
if (target.name) {
const item = this.addProperty('name', target.name);
if (target.type === 'function') {
item.action('\u0192', 'Show Function Documentation', () => {
this.emit('show-definition', null);
});
}
}
if (signature && signature.name) {
this.addProperty('signature', signature.name);
}
if (target.version) {
this.addProperty('version', target.version);
}
if (target.description) {
this.addProperty('description', target.description);
}
const attributes = signature ? signature.attributes : target.attributes;
const inputs = signature ? signature.inputs : target.inputs;
const outputs = signature ? signature.outputs : target.outputs;
if (Array.isArray(attributes) && attributes.length > 0) {
this.addSection('Attributes');
for (const attribute of attributes) {
this.addProperty(attribute.name, attribute.value);
}
}
if (Array.isArray(inputs) && inputs.length > 0) {
this.addSection('Inputs');
for (const input of inputs) {
this.addArgument(input.name, input);
}
}
if (Array.isArray(outputs) && outputs.length > 0) {
this.addSection('Outputs');
for (const output of outputs) {
this.addArgument(output.name, output);
}
}
const metadata = this._view.model.attachment.metadata.graph(target);
if (Array.isArray(metadata) && metadata.length > 0) {
this.addSection('Metadata');
for (const argument of metadata) {
this.addArgument(argument.name, argument, 'attribute');
}
}
const metrics = this.metrics;
if (Array.isArray(metrics) && metrics.length > 0) {
this.addSection('Metrics');
for (const argument of metrics) {
this.addArgument(argument.name, argument, 'attribute');
}
}
}
get metrics() {
const target = new metrics.Target(this._target);
return this._view.model.attachment.metrics.graph(target);
}
get identifier() {
return 'target';
}
addArgument(name, argument, source) {
const value = super.addArgument(name, argument, source);
value.toggle();
return value;
}
};
view.DocumentationSidebar = class extends view.Control {
constructor(context, type) {
super(context);
this._type = type;
this._escapeReplacementsMap = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
this._escapeTestNoEncodeRegExp = /[<>"']|&(?!#?\w+;)/;
this._escapeReplaceNoEncodeRegExp = /[<>"']|&(?!#?\w+;)/g;
}
get identifier() {
return 'documentation';
}
render() {
if (!this.element) {
this.element = this.createElement('div', 'sidebar-documentation');
const type = view.Documentation.open(this._type);
this._append(this.element, 'h1', type.name);
if (type.summary) {
this._append(this.element, 'p', type.summary);
}
if (type.description) {
this._append(this.element, 'p', type.description);
}
if (Array.isArray(type.attributes) && type.attributes.length > 0) {
this._append(this.element, 'h2', 'Attributes');
const attributes = this._append(this.element, 'dl');
for (const attribute of type.attributes) {
this._append(attributes, 'dt', attribute.name + (attribute.type ? `: ${this._escape(attribute.type)}` : ''));
this._append(attributes, 'dd', attribute.description);
}
this.element.appendChild(attributes);
}
if (Array.isArray(type.inputs) && type.inputs.length > 0) {
this._append(this.element, 'h2', `Inputs${type.inputs_range ? ` (${type.inputs_range})` : ''}`);
const inputs = this._append(this.element, 'dl');
for (const input of type.inputs) {
this._append(inputs, 'dt', input.name + (input.type ? `: ${this._escape(input.type)}` : '') + (input.option ? ` (${input.option})` : ''));
this._append(inputs, 'dd', input.description);
}
}
if (Array.isArray(type.outputs) && type.outputs.length > 0) {
this._append(this.element, 'h2', `Outputs${type.outputs_range ? ` (${type.outputs_range})` : ''}`);
const outputs = this._append(this.element, 'dl');
for (const output of type.outputs) {
this._append(outputs, 'dt', output.name + (output.type ? `: ${this._escape(output.type)}` : '') + (output.option ? ` (${output.option})` : ''));
this._append(outputs, 'dd', output.description);
}
}
if (Array.isArray(type.type_constraints) && type.type_constraints.length > 0) {
this._append(this.element, 'h2', 'Type Constraints');
const type_constraints = this._append(this.element, 'dl');
for (const type_constraint of type.type_constraints) {
this._append(type_constraints, 'dt', `${type_constraint.type_param_str}: ${type_constraint.allowed_type_strs.map((item) => `${item}`).join(', ')}`);
this._append(type_constraints, 'dd', type_constraint.description);
}
}
if (Array.isArray(type.examples) && type.examples.length > 0) {
this._append(this.element, 'h2', 'Examples');
for (const example of type.examples) {
this._append(this.element, 'h3', example.summary);
this._append(this.element, 'pre', example.code);
}
}
if (Array.isArray(type.references) && type.references.length > 0) {
this._append(this.element, 'h2', 'References');
const references = this._append(this.element, 'ul');
for (const reference of type.references) {
this._append(references, 'li', reference.description);
}
}
if (this._host.type === 'Electron') {
this.element.addEventListener('click', (e) => {
if (e.target && e.target.href) {
const url = e.target.href;
if (url.startsWith('http://') || url.startsWith('https://')) {
e.preventDefault();
this.emit('navigate', { link: url });
}
}
});
}
}
}
_append(parent, type, content) {
const element = this.createElement(type);
if (content) {
element.innerHTML = content;
}
parent.appendChild(element);
return element;
}
_escape(content) {
if (this._escapeTestNoEncodeRegExp.test(content)) {
return content.replace(this._escapeReplaceNoEncodeRegExp, (ch) => this._escapeReplacementsMap[ch]);
}
return content;
}
error(error, fatal) {
super.error(error, fatal);
const element = this.createElement('span');
const title = this.createElement('b');
title.textContent = 'ERROR: ';
element.appendChild(title);
const message = this.createTextNode(error.message);
element.appendChild(message);
this.element.appendChild(element);
}
};
view.FindSidebar = class extends view.Control {
constructor(context, state, graph, signature) {
super(context);
this._target = graph;
this._signature = signature;
this._state = state || {
query: '',
node: true,
connection: true,
weight: true
};
this._toggles = {
node: { hide: 'Hide Nodes', show: 'Show Nodes' },
connection: { hide: 'Hide Connections', show: 'Show Connections' },
weight: { hide: 'Hide Weights', show: 'Show Weights' }
};
}
get identifier() {
return 'find';
}
on(event, callback) {
this._events = this._events || {};
this._events[event] = this._events[event] || [];
this._events[event].push(callback);
}
emit(event, data) {
try {
if (this._events && this._events[event]) {
for (const callback of this._events[event]) {
callback(this, data);
}
}
} catch (error) {
this.error(error, false);
}
}
_reset() {
for (const element of this._focused) {
this._blur(element);
}
this._focused.clear();
this._table.clear();
this._content.replaceChildren();
this._edges.clear();
for (const value of Object.values(this._toggles)) {
delete value.template;
}
const unquote = this._state.query.match(new RegExp(/^'(.*)'|"(.*)"$/));
if (unquote) {
this._exact = true;
const term = unquote[1] || unquote[2];
this._terms = [term];
} else {
this._exact = false;
this._terms = this._state.query.trim().toLowerCase().split(' ').map((term) => term.trim()).filter((term) => term.length > 0);
}
}
_term(value) {
if (this._exact) {
return value === this._terms[0];
}
value = value.toLowerCase();
return this._terms.every((term) => value.indexOf(term) !== -1);
}
_value(value) {
if (this._terms.length === 0) {
return true;
}
if (value.name && this._term(value.name.split('\n').shift())) {
return true;
}
if (value.identifier && this._term(value.identifier)) {
return true;
}
if (value.type && !this._exact) {
for (const term of this._terms) {
if (value.type.dataType && term === value.type.dataType.toLowerCase()) {
return true;
}
if (value.type.shape) {
if (term === value.type.shape.toString().toLowerCase()) {
return true;
}
if (value.type.shape && Array.isArray(value.type.shape.dimensions)) {
const dimensions = value.type.shape.dimensions.map((dimension) => dimension ? dimension.toString().toLowerCase() : '');
if (term === dimensions.join(',')) {
return true;
}
if (dimensions.some((dimension) => term === dimension)) {
return true;
}
}
}
}
}
return false;
}
_edge(value) {
if (value.name && !this._edges.has(value.name) && this._value(value)) {
const content = `${value.name.split('\n').shift()}`;
this._add(value, content, 'connection'); // split custom argument id
this._edges.add(value.name);
}
}
_node(node) {
if (this._state.connection) {
const inputs = node.inputs;
if (Array.isArray(inputs)) {
for (const input of node.inputs) {
if (!input.type || input.type.endsWith('*')) {
for (const value of input.value) {
if (value !== null && !value.initializer) {
this._edge(value);
}
}
}
}
}
}
if (this._state.node) {
const name = node.name;
const type = node.type.name;
const identifier = node.identifier;
if ((name && this._term(name)) || (type && this._term(type)) || (identifier && this._term(identifier))) {
const content = `${name || `[${type}]`}`;
this._add(node, content, 'node');
}
}
if (this._state.weight) {
const inputs = node.inputs;
if (Array.isArray(inputs)) {
for (const argument of node.inputs) {
if (!argument.type || argument.type.endsWith('*')) {
for (const value of argument.value) {
if (value !== null && value.initializer && this._value(value)) {
let content = null;
if (value.name) {
content = `${value.name.split('\n').shift()}`; // split custom argument id
} else if (Array.isArray(argument.value) && argument.value.length === 1 && argument.name.indexOf('.') !== -1) {
content = argument.name;
} else if (value.type && value.type.shape && Array.isArray(value.type.shape.dimensions) && value.type.shape.dimensions.length > 0) {
content = `${value.type.shape.dimensions.map((d) => (d !== null && d !== undefined) ? d : '?').join('\u00D7')}`;
}
if (content) {
const target = argument.value.length === 1 ? argument : node;
this._add(target, content, 'weight');
}
}
}
} else if (argument.type === 'object') {
this._node(argument.value);
} else if (argument.type === 'object[]') {
for (const value of argument.value) {
this._node(value);
}
}
}
}
}
}
_add(value, content, type) {
if (!this._toggles[type].template) {
const element = this.createElement('li');
element.innerHTML = ``;
this._toggles[type].template = element;
}
const element = this._toggles[type].template.cloneNode(true);
const text = this._host.document.createTextNode(content);
element.appendChild(text);
this._table.set(element, value);
this._content.appendChild(element);
}
_focus(element) {
if (this._table.has(element)) {
this.emit('focus', this._table.get(element));
this._focused.add(element);
}
}
_blur(element) {
if (this._table.has(element)) {
this.emit('blur', this._table.get(element));
this._focused.delete(element);
}
}
_update() {
try {
this._reset();
const inputs = this._signature ? this._signature.inputs : this._target.inputs;
if (this._state.connection) {
for (const input of inputs) {
for (const value of input.value) {
this._edge(value);
}
}
}
for (const node of this._target.nodes) {
this._node(node);
}
if (this._state.connection) {
const outputs = this._signature ? this._signature.outputs : this._target.outputs;
for (const output of outputs) {
if (!output.type || output.type.endsWith('*')) {
for (const value of output.value) {
this._edge(value);
}
}
}
}
} catch (error) {
this.error(error, false);
}
}
render() {
this._table = new Map();
this._focused = new Set();
this._edges = new Set();
this._search = this.createElement('div', 'sidebar-find-search');
this._query = this.createElement('input', 'sidebar-find-query');
this._search.appendChild(this._query);
this._content = this.createElement('ol', 'sidebar-find-content');
this._elements = [this._query, this._content];
this._query.setAttribute('id', 'search');
this._query.setAttribute('type', 'text');
this._query.setAttribute('spellcheck', 'false');
this._query.setAttribute('placeholder', 'Search');
this._query.addEventListener('input', (e) => {
this._state.query = e.target.value;
this.emit('state-changed', this._state);
this._update();
});
this._query.addEventListener('keydown', (e) => {
if (e.keyCode === 0x08 && !e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey) {
e.stopPropagation();
}
});
for (const [name, toggle] of Object.entries(this._toggles)) {
toggle.element = this.createElement('label', 'sidebar-find-toggle');
toggle.element.innerHTML = ``;
toggle.element.setAttribute('title', this._state[name] ? toggle.hide : toggle.show);
toggle.checkbox = this.createElement('input');
toggle.checkbox.setAttribute('type', 'checkbox');
toggle.checkbox.setAttribute('data', name);
toggle.checkbox.addEventListener('change', (e) => {
const name = e.target.getAttribute('data');
this._state[name] = e.target.checked;
const toggle = this._toggles[name];
toggle.element.setAttribute('title', e.target.checked ? toggle.hide : toggle.show);
this.emit('state-changed', this._state);
this._update();
});
toggle.element.insertBefore(toggle.checkbox, toggle.element.firstChild);
this._search.appendChild(toggle.element);
}
this._content.addEventListener('click', (e) => {
if (this._table.has(e.target)) {
this.emit('select', this._table.get(e.target));
}
});
this._content.addEventListener('dblclick', (e) => {
if (this._table.has(e.target)) {
this.emit('activate', this._table.get(e.target));
}
});
this._content.addEventListener('pointerover', (e) => {
for (const element of this._focused) {
this._blur(element);
}
this._focus(e.target);
});
}
get element() {
return [this._search, this._content];
}
activate() {
this._query.focus();
this._query.value = '';
this._query.value = this._state.query;
for (const [name, toggle] of Object.entries(this._toggles)) {
toggle.checkbox.checked = this._state[name];
toggle.element.setAttribute('title', this._state[name] ? toggle.hide : toggle.show);
}
this._update();
this._host.event('open_sidebar', {
sidebar_identifier: this.identifier,
sidebar_size: this._table.size
});
}
deactivate() {
this._reset();
}
error(error, fatal) {
super.error(error, fatal);
const element = this.createElement('li');
const title = this.createElement('b');
title.textContent = 'ERROR: ';
element.appendChild(title);
const message = this.createTextNode(` ${error.message}`);
element.appendChild(message);
this._content.appendChild(element);
}
};
view.Quantization = class {
constructor(quantization) {
Object.assign(this, quantization);
}
toString() {
if (this.type === 'linear' || /^quant\d\d?_.*$/.test(this.type)) {
const content = [];
const scale = this.scale || [];
const offset = this.offset || [];
const bias = this.bias || [];
const max = this.max || [];
const min = this.min || [];
const length = Math.max(scale.length, offset.length, bias.length, min.length, max.length);
const size = length.toString().length;
for (let i = 0; i < length; i++) {
let s = 'q';
let bracket = false;
if (i < offset.length && offset[i] !== undefined && offset[i] !== 0 && offset[i] !== 0n) {
const value = offset[i];
s = value > 0 ? `${s} - ${value}` : `${s} + ${-value}`;
bracket = true;
}
if (i < scale.length && scale[i] !== undefined && scale[i] !== 1 && scale[i] !== 1n) {
const value = scale[i];
s = bracket ? `(${s})` : s;
s = `${value} * ${s}`;
bracket = true;
}
if (i < bias.length && bias[i] !== undefined && bias[i] !== 0 && bias[i] !== 0n) {
const value = bias[i];
s = bracket ? `(${s})` : s;
s = value < 0 ? `${s} - ${-value}` : `${s} + ${value}`;
}
if (i < min.length && min[i] !== undefined && min[i] !== 0 && min[i] !== 0n) {
s = `${min[i]} \u2264 ${s}`;
}
if (i < max.length && max[i] !== undefined && max[i] !== 0 && max[i] !== 0n) {
s = `${s} \u2264 ${max[i]}`;
}
content.push(length > 1 ? `${i.toString().padStart(size, ' ')}: ${s}` : `${s}`);
}
return content.join('\n');
} else if (this.type === 'lookup') {
const size = this.value.length.toString().length;
return this.value.map((value, index) => `${index.toString().padStart(size, ' ')}: ${value}`).join('\n');
} else if (this.type === 'annotation') {
return Array.from(this.value).map(([name, value]) => `${name} = ${value}`).join('\n');
} else if (/^q\d_[01k]$/.test(this.type) || /^iq\d_[xsnlm]+$/.test(this.type) || this.type === 'mxfp4') {
return '';
}
throw new view.Error(`Unknown quantization type '${this.type}'.`);
}
};
view.Documentation = class {
static open(source) {
if (source) {
const generator = markdown.Generator.open();
const target = {};
if (source.name) {
target.name = source.name;
}
if (source.module) {
target.module = source.module;
}
if (source.category) {
target.category = source.category;
}
if (source.summary) {
target.summary = generator.html(source.summary);
}
if (source.description) {
target.description = generator.html(source.description);
}
if (Array.isArray(source.attributes)) {
target.attributes = source.attributes.map((source) => {
const target = {};
target.name = source.name;
if (source.type !== undefined) {
target.type = source.type === null || typeof source.type === 'string' ? source.type : source.type.toString();
}
if (source.option !== undefined) {
target.option = source.option;
}
if (source.optional !== undefined) {
target.optional = source.optional;
}
if (source.required !== undefined) {
target.required = source.required;
}
if (source.minimum !== undefined) {
target.minimum = source.minimum;
}
if (source.src !== undefined) {
target.src = source.src;
}
if (source.src_type !== undefined) {
target.src_type = source.src_type;
}
if (source.description) {
target.description = generator.html(source.description);
}
if (source.default !== undefined) {
target.default = source.default;
}
if (source.visible !== undefined) {
target.visible = source.visible;
}
return target;
});
}
if (Array.isArray(source.inputs)) {
target.inputs = source.inputs.map((source) => {
const target = {};
target.name = source.name;
if (source.type !== undefined) {
target.type = source.type === null || typeof source.type === 'string' ? source.type : source.type.toString();
}
if (source.description) {
target.description = generator.html(source.description);
}
if (source.default !== undefined) {
target.default = source.default;
}
if (source.src !== undefined) {
target.src = source.src;
}
if (source.list !== undefined) {
target.list = source.list;
}
if (source.isRef !== undefined) {
target.isRef = source.isRef;
}
if (source.typeAttr !== undefined) {
target.typeAttr = source.typeAttr;
}
if (source.numberAttr !== undefined) {
target.numberAttr = source.numberAttr;
}
if (source.typeListAttr !== undefined) {
target.typeListAttr = source.typeListAttr;
}
if (source.option !== undefined) {
target.option = source.option;
}
if (source.optional !== undefined) {
target.optional = source.optional;
}
if (source.visible !== undefined) {
target.visible = source.visible;
}
return target;
});
}
if (Array.isArray(source.outputs)) {
target.outputs = source.outputs.map((source) => {
const target = {};
target.name = source.name;
if (source.type) {
target.type = source.type === null || typeof source.type === 'string' ? source.type : source.type.toString();
}
if (source.description) {
target.description = generator.html(source.description);
}
if (source.list !== undefined) {
target.list = source.list;
}
if (source.typeAttr !== undefined) {
target.typeAttr = source.typeAttr;
}
if (source.typeListAttr !== undefined) {
target.typeListAttr = source.typeListAttr;
}
if (source.numberAttr !== undefined) {
target.numberAttr = source.numberAttr;
}
if (source.isRef !== undefined) {
target.isRef = source.isRef;
}
if (source.option !== undefined) {
target.option = source.option;
}
return target;
});
}
if (Array.isArray(source.references)) {
target.references = source.references.map((source) => {
if (source) {
target.description = generator.html(source.description);
}
return target;
});
}
if (source.version !== undefined) {
target.version = source.version;
}
if (source.operator !== undefined) {
target.operator = source.operator;
}
if (source.identifier !== undefined) {
target.identifier = source.identifier;
}
if (source.package !== undefined) {
target.package = source.package;
}
if (source.status !== undefined) {
target.status = source.status;
}
if (source.min_input !== undefined) {
target.min_input = source.min_input;
}
if (source.max_input !== undefined) {
target.max_input = source.max_input;
}
if (source.min_output !== undefined) {
target.min_output = source.min_output;
}
if (source.max_output !== undefined) {
target.max_output = source.max_output;
}
if (source.inputs_range !== undefined) {
target.inputs_range = source.inputs_range;
}
if (source.outputs_range !== undefined) {
target.outputs_range = source.outputs_range;
}
if (source.examples !== undefined) {
target.examples = source.examples;
}
if (source.constants !== undefined) {
target.constants = source.constants;
}
if (source.type_constraints !== undefined) {
target.type_constraints = source.type_constraints;
}
return target;
}
return null;
}
};
view.Formatter = class {
constructor(value, type, quote) {
this._value = value;
this._type = type;
this._quote = quote;
this._values = new Set();
}
toString() {
return this._format(this._value, this._type, this._quote);
}
_format(value, type, quote) {
if (value && value.__class__ && value.__class__.__module__ === 'builtins' && value.__class__.__name__ === 'type') {
return `${value.__module__}.${value.__name__}`;
}
if (value && value.__class__ && value.__class__.__module__ === 'builtins' && value.__class__.__name__ === 'function') {
return `${value.__module__}.${value.__name__}`;
}
if (typeof value === 'function') {
return value();
}
if (value !== null && value !== undefined && (typeof value === 'bigint' || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean')) {
return value.toString();
}
if (Number.isNaN(value)) {
return 'NaN';
}
switch (type) {
case 'shape':
return value ? value.toString() : '(null)';
case 'shape[]':
if (value && !Array.isArray(value)) {
throw new Error(`Invalid shape '${JSON.stringify(value)}'.`);
}
return value ? value.map((item) => item.toString()).join(', ') : '(null)';
case 'graph':
return value ? value.name : '(null)';
case 'graph[]':
return value ? value.map((graph) => graph.name).join(', ') : '(null)';
case 'tensor': {
if (value === null) {
return '(null)';
}
return view.Formatter.tensor(value);
}
case 'object':
return value.type.name;
case 'function':
return value.name;
case 'object[]':
case 'function[]':
return value ? value.map((item) => item.type.name).join(', ') : '(null)';
case 'type':
return value ? value.toString() : '(null)';
case 'type[]':
return value ? value.map((item) => item.toString()).join(', ') : '(null)';
case 'complex':
return value ? value.toString() : '(null)';
default:
break;
}
if (typeof value === 'string' && (!type || type !== 'string')) {
if (quote) {
return `"${value}"`;
}
if (value.trim().length === 0) {
return value;
}
return value;
}
if (Array.isArray(value)) {
if (value.length === 0) {
return quote ? '[]' : '';
}
let ellipsis = false;
if (value.length > 2000) {
value = value.slice(0, 2000);
ellipsis = true;
}
const itemType = (type && type.endsWith('[]')) ? type.substring(0, type.length - 2) : null;
const array = value.map((value) => {
if (value && typeof value === 'bigint') {
return value.toString();
}
if (Number.isNaN(value)) {
return 'NaN';
}
if (value && value.constructor && value.constructor.name === 'Value' && value.name) {
return `{${value.name}}`;
}
const quote = !itemType || itemType === 'string';
return this._format(value, itemType, quote);
});
if (ellipsis) {
array.push('\u2026');
}
return quote ? ['[', array.join(', '), ']'].join(' ') : array.join(', ');
}
if (value === null) {
return quote ? 'null' : '';
}
if (value === undefined) {
return 'undefined';
}
if (value !== Object(value)) {
return value.toString();
}
if (this._values.has(value)) {
return '\u2026';
}
this._values.add(value);
let list = null;
const map = value instanceof Map ? Array.from(value) : Object.entries(value);
const entries = map.filter(([name]) => typeof name === 'string' && !name.startsWith('__') && !name.endsWith('__'));
if (entries.length === 1) {
list = [this._format(entries[0][1], null, true)];
} else {
list = entries.map(([name, value]) => `${name}: ${this._format(value, null, true)}`);
}
let objectType = value.__type__;
if (!objectType && value.constructor.name && value.constructor.name !== 'Object') {
objectType = value.constructor.name;
}
if (objectType) {
return objectType + (list.length === 0 ? '()' : ['(', list.join(', '), ')'].join(''));
}
switch (list.length) {
case 0:
return quote ? '()' : '';
case 1:
return list[0];
default:
return quote ? ['(', list.join(', '), ')'].join(' ') : list.join(', ');
}
}
static tensor(value) {
const type = value.type;
if (type && type.shape && type.shape.dimensions && Array.isArray(type.shape.dimensions)) {
if (type.shape.dimensions.length === 0 && (!value.peek || value.peek() === true)) {
const tensor = new base.Tensor(value);
const encoding = tensor.encoding;
if ((encoding === '<' || encoding === '>' || encoding === '|') && !tensor.empty && tensor.type.dataType !== '?') {
let content = tensor.toString();
if (content && content.length > 10) {
content = `${content.substring(0, 10)}\u2026`;
}
return content;
}
}
const content = type.shape.dimensions.map((d) => (d !== null && d !== undefined) ? d : '?').join('\u00D7');
return `\u3008${content}\u3009`;
}
return '\u3008\u2026\u3009';
}
};
markdown.Generator = class {
static open() {
if (!markdown.Generator.generator) {
markdown.Generator.generator = new markdown.Generator();
}
return markdown.Generator.generator;
}
constructor() {
this._newlineRegExp = /^\n+/;
this._codeRegExp = /^( {4}[^\n]+\n*)+/;
this._fencesRegExp = /^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?:\n+|$)|$)/;
this._hrRegExp = /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/;
this._headingRegExp = /^ {0,3}(#{1,6}) +([^\n]*?)(?: +#+)? *(?:\n+|$)/;
this._blockquoteRegExp = /^( {0,3}> ?(([^\n]+(?:\n(?! {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)| {0,3}#{1,6} | {0,3}>| {0,3}(?:`{3,}(?=[^`\n]*\n)|~{3,})[^\n]*\n| {0,3}(?:[*+-]|1[.)]) |<\/?(?:address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(?: +|\n|\/?>)|<(?:script|pre|style|!--))[^\n]+)*)|[^\n]*)(?:\n|$))+/;
this._listRegExp = /^( {0,3})((?:[*+-]|\d{1,9}[.)])) [\s\S]+?(?:\n+(?=\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$))|\n+(?= {0,3}\[((?!\s*\])(?:\\[[\]]|[^[\]])+)\]: *\n? *([^\s>]+)>?(?:(?: +\n? *| *\n *)((?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))))? *(?:\n+|$))|\n{2,}(?! )(?!\1(?:[*+-]|\d{1,9}[.)]) )\n*|\s*$)/;
this._htmlRegExp = /^ {0,3}(?:<(script|pre|style)[\s>][\s\S]*?(?:<\/\1>[^\n]*\n+|$)||$)[^\n]*(\n+|$)|<\?[\s\S]*?(?:\?>\n*|$)|\n*|$)|\n*|$)|<\/?(address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(?: +|\n|\/?>)[\s\S]*?(?:\n{2,}|$)|<(?!script|pre|style)([a-z][\w-]*)(?: +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?)*? *\/?>(?=[ \t]*(?:\n|$))[\s\S]*?(?:\n{2,}|$)|<\/(?!script|pre|style)[a-z][\w-]*\s*>(?=[ \t]*(?:\n|$))[\s\S]*?(?:\n{2,}|$))/i;
this._defRegExp = /^ {0,3}\[((?!\s*\])(?:\\[[\]]|[^[\]])+)\]: *\n? *([^\s>]+)>?(?:(?: +\n? *| *\n *)((?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))))? *(?:\n+|$)/;
this._nptableRegExp = /^ *([^|\n ].*\|.*)\n {0,3}([-:]+ *\|[-| :]*)(?:\n((?:(?!\n| {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)| {0,3}#{1,6} | {0,3}>| {4}[^\n]| {0,3}(?:`{3,}(?=[^`\n]*\n)|~{3,})[^\n]*\n| {0,3}(?:[*+-]|1[.)]) |<\/?(?:address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(?: +|\n|\/?>)|<(?:script|pre|style|!--)).*(?:\n|$))*)\n*|$)/;
this._tableRegExp = /^ *\|(.+)\n {0,3}\|?( *[-:]+[-| :]*)(?:\n *((?:(?!\n| {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)| {0,3}#{1,6} | {0,3}>| {4}[^\n]| {0,3}(?:`{3,}(?=[^`\n]*\n)|~{3,})[^\n]*\n| {0,3}(?:[*+-]|1[.)]) |<\/?(?:address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(?: +|\n|\/?>)|<(?:script|pre|style|!--)).*(?:\n|$))*)\n*|$)/;
this._lheadingRegExp = /^([^\n]+)\n {0,3}(=+|-+) *(?:\n+|$)/;
this._textRegExp = /^[^\n]+/;
this._bulletRegExp = /(?:[*+-]|\d{1,9}[.)])/;
this._itemRegExp = /^( *)((?:[*+-]|\d{1,9}[.)])) ?[^\n]*(?:\n(?!\1(?:[*+-]|\d{1,9}[.)]) ?)[^\n]*)*/gm;
this._paragraphRegExp = /^([^\n]+(?:\n(?! {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)| {0,3}#{1,6} | {0,3}>| {0,3}(?:`{3,}(?=[^`\n]*\n)|~{3,})[^\n]*\n| {0,3}(?:[*+-]|1[.)]) |<\/?(?:address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(?: +|\n|\/?>)|<(?:script|pre|style|!--))[^\n]+)*)/;
this._backpedalRegExp = /(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/;
this._escapeRegExp = /^\\([!"#$%&'()*+,\-./:;<=>?@[\]\\^_`{|}~~|])/;
this._escapesRegExp = /\\([!"#$%&'()*+,\-./:;<=>?@[\]\\^_`{|}~])/g;
/* eslint-disable no-control-regex */
this._autolinkRegExp = /^<([a-zA-Z][a-zA-Z0-9+.-]{1,31}:[^\s\x00-\x1f<>]*|[a-zA-Z0-9.!#$%&'*+/=?_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_]))>/;
this._linkRegExp = /^!?\[((?:\[(?:\\.|[^[\]\\])*\]|\\.|`[^`]*`|[^[\]\\`])*?)\]\(\s*(<(?:\\[<>]?|[^\s<>\\])*>|[^\s\x00-\x1f]*)(?:\s+("(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)))?\s*\)/;
/* eslint-enable no-control-regex */
this._urlRegExp = /^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9-]+\.?)+[^\s<]*|^[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/i;
this._tagRegExp = /^|^<\/[a-zA-Z][\w:-]*\s*>|^<[a-zA-Z][\w-]*(?:\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?)*?\s*\/?>|^<\?[\s\S]*?\?>|^|^/;
this._reflinkRegExp = /^!?\[((?:\[(?:\\.|[^[\]\\])*\]|\\.|`[^`]*`|[^[\]\\`])*?)\]\[(?!\s*\])((?:\\[[\]]?|[^[\]\\])+)\]/;
this._nolinkRegExp = /^!?\[(?!\s*\])((?:\[[^[\]]*\]|\\[[\]]|[^[\]])*)\](?:\[\])?/;
this._reflinkSearchRegExp = /!?\[((?:\[(?:\\.|[^[\]\\])*\]|\\.|`[^`]*`|[^[\]\\`])*?)\]\[(?!\s*\])((?:\\[[\]]?|[^[\]\\])+)\]|!?\[(?!\s*\])((?:\[[^[\]]*\]|\\[[\]]|[^[\]])*)\](?:\[\])?(?!\()/g;
this._strongStartRegExp = /^(?:(\*\*(?=[*!"#$%&'()+\-.,/:;<=>?@[\]`{|}~]))|\*\*)(?![\s])|__/;
this._strongMiddleRegExp = /^\*\*(?:(?:(?!__[^_]*?__|\*\*\[^\*\]*?\*\*)(?:[^*]|\\\*)|__[^_]*?__|\*\*\[^\*\]*?\*\*)|\*(?:(?!__[^_]*?__|\*\*\[^\*\]*?\*\*)(?:[^*]|\\\*)|__[^_]*?__|\*\*\[^\*\]*?\*\*)*?\*)+?\*\*$|^__(?![\s])((?:(?:(?!__[^_]*?__|\*\*\[^\*\]*?\*\*)(?:[^_]|\\_)|__[^_]*?__|\*\*\[^\*\]*?\*\*)|_(?:(?!__[^_]*?__|\*\*\[^\*\]*?\*\*)(?:[^_]|\\_)|__[^_]*?__|\*\*\[^\*\]*?\*\*)*?_)+?)__$/;
this._strongEndAstRegExp = /[^!"#$%&'()+\-.,/:;<=>?@[\]`{|}~\s]\*\*(?!\*)|[!"#$%&'()+\-.,/:;<=>?@[\]`{|}~]\*\*(?!\*)(?:(?=[!"#$%&'()+\-.,/:;<=>?@[\]`{|}~_\s]|$))/g;
this._strongEndUndRegExp = /[^\s]__(?!_)(?:(?=[!"#$%&'()+\-.,/:;<=>?@[\]`{|}~*\s])|$)/g;
this._emStartRegExp = /^(?:(\*(?=[!"#$%&'()+\-.,/:;<=>?@[\]`{|}~]))|\*)(?![*\s])|_/;
this._emMiddleRegExp = /^\*(?:(?:(?!__[^_]*?__|\*\*\[^\*\]*?\*\*)(?:[^*]|\\\*)|__[^_]*?__|\*\*\[^\*\]*?\*\*)|\*(?:(?!__[^_]*?__|\*\*\[^\*\]*?\*\*)(?:[^*]|\\\*)|__[^_]*?__|\*\*\[^\*\]*?\*\*)*?\*)+?\*$|^_(?![_\s])(?:(?:(?!__[^_]*?__|\*\*\[^\*\]*?\*\*)(?:[^_]|\\_)|__[^_]*?__|\*\*\[^\*\]*?\*\*)|_(?:(?!__[^_]*?__|\*\*\[^\*\]*?\*\*)(?:[^_]|\\_)|__[^_]*?__|\*\*\[^\*\]*?\*\*)*?_)+?_$/;
this._emEndAstRegExp = /[^!"#$%&'()+\-.,/:;<=>?@[\]`{|}~\s]\*(?!\*)|[!"#$%&'()+\-.,/:;<=>?@[\]`{|}~]\*(?!\*)(?:(?=[!"#$%&'()+\-.,/:;<=>?@[\]`{|}~_\s]|$))/g;
this._emEndUndRegExp = /[^\s]_(?!_)(?:(?=[!"#$%&'()+\-.,/:;<=>?@[\]`{|}~*\s])|$)/g;
this._codespanRegExp = /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/;
this._brRegExp = /^( {2,}|\\)\n(?!\s*$)/;
this._delRegExp = /^~+(?=\S)([\s\S]*?\S)~+/;
this._textspanRegExp = /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\?@[\]`{|}~])/;
this._blockSkipRegExp = /\[[^\]]*?\]\([^)]*?\)|`[^`]*?`|<[^>]*?>/g;
this._escapeTestRegExp = /[&<>"']/;
this._escapeReplaceRegExp = /[&<>"']/g;
this._escapeTestNoEncodeRegExp = /[<>"']|&(?!#?\w+;)/;
this._escapeReplaceNoEncodeRegExp = /[<>"']|&(?!#?\w+;)/g;
this._escapeReplacementsMap = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
this._cache = new Map();
}
html(source) {
if (this._cache.has(source)) {
return this._cache.get(source);
}
const tokens = [];
const links = new Map();
source = source.replace(/\r\n|\r/g, '\n').replace(/\t/g, ' ');
this._tokenize(source, tokens, links, true);
this._tokenizeBlock(tokens, links);
const target = this._render(tokens, true);
if (this._cache.size > 256) {
this._cache.delete(this._cache.keys().next().value);
}
this._cache.set(source, target);
return target;
}
_tokenize(source, tokens, links, top) {
source = source.replace(/^ +$/gm, '');
while (source) {
let match = this._newlineRegExp.exec(source);
if (match) {
source = source.substring(match[0].length);
if (match[0].length > 1) {
tokens.push({ type: 'space' });
}
continue;
}
match = this._codeRegExp.exec(source);
if (match) {
source = source.substring(match[0].length);
const lastToken = tokens[tokens.length - 1];
if (lastToken && lastToken.type === 'paragraph') {
lastToken.text += `\n${match[0].trimRight()}`;
} else {
const text = match[0].replace(/^ {4}/gm, '').replace(/\n*$/, '');
tokens.push({ type: 'code', text });
}
continue;
}
match = this._fencesRegExp.exec(source);
if (match) {
source = source.substring(match[0].length);
const language = match[2] ? match[2].trim() : match[2];
let content = match[3] || '';
const matchIndent = match[0].match(/^(\s+)(?:```)/);
if (matchIndent !== null) {
const [, indent] = matchIndent;
content = content.split('\n').map((node) => {
const match = node.match(/^\s+/);
return (match !== null && match[0].length >= indent.length) ? node.slice(indent.length) : node;
}).join('\n');
}
tokens.push({ type: 'code', language, text: content });
continue;
}
match = this._headingRegExp.exec(source);
if (match) {
source = source.substring(match[0].length);
tokens.push({ type: 'heading', depth: match[1].length, text: match[2] });
continue;
}
match = this._nptableRegExp.exec(source);
if (match) {
const header = this._splitCells(match[1].replace(/^ *| *\| *$/g, ''));
const align = match[2].replace(/^ *|\| *$/g, '').split(/ *\| */);
if (header.length === align.length) {
const cells = match[3] ? match[3].replace(/\n$/, '').split('\n') : [];
const token = { type: 'table', header, align, cells, raw: match[0] };
for (let i = 0; i < token.align.length; i++) {
if (/^ *-+: *$/.test(token.align[i])) {
token.align[i] = 'right';
} else if (/^ *:-+: *$/.test(token.align[i])) {
token.align[i] = 'center';
} else if (/^ *:-+ *$/.test(token.align[i])) {
token.align[i] = 'left';
} else {
token.align[i] = null;
}
}
token.cells = token.cells.map((cell) => this._splitCells(cell, token.header.length));
source = source.substring(token.raw.length);
tokens.push(token);
continue;
}
}
match = this._hrRegExp.exec(source);
if (match) {
source = source.substring(match[0].length);
tokens.push({ type: 'hr' });
continue;
}
match = this._blockquoteRegExp.exec(source);
if (match) {
source = source.substring(match[0].length);
const text = match[0].replace(/^ *> ?/gm, '');
tokens.push({ type: 'blockquote', text, tokens: this._tokenize(text, [], links, top) });
continue;
}
match = this._listRegExp.exec(source);
if (match) {
const [value, , bull] = match;
const ordered = bull.length > 1;
const parent = bull[bull.length - 1] === ')';
let raw = value;
const list = { type: 'list', raw, ordered, start: ordered ? Number(bull.slice(0, -1)) : '', loose: false, items: [] };
const itemMatch = value.match(this._itemRegExp);
let next = false;
const length = itemMatch.length;
for (let i = 0; i < length; i++) {
let item = itemMatch[i];
raw = item;
let space = item.length;
item = item.replace(/^ *([*+-]|\d+[.)]) ?/, '');
if (item.indexOf('\n ') !== -1) {
space -= item.length;
item = item.replace(new RegExp(`^ {1,${space}}`, 'gm'), '');
}
if (i !== length - 1) {
const [bullet] = this._bulletRegExp.exec(itemMatch[i + 1]);
if (ordered ? bullet.length === 1 || (!parent && bullet[bullet.length - 1] === ')') : (bullet.length > 1)) {
const addBack = itemMatch.slice(i + 1).join('\n');
list.raw = list.raw.substring(0, list.raw.length - addBack.length);
i = length - 1;
}
}
let loose = next || /\n\n(?!\s*$)/.test(item);
if (i !== length - 1) {
next = item.charAt(item.length - 1) === '\n';
if (!loose) {
loose = next;
}
}
if (loose) {
list.loose = true;
}
const task = /^\[[ xX]\] /.test(item);
let checked = false;
if (task) {
checked = item[1] !== ' ';
item = item.replace(/^\[[ xX]\] +/, '');
}
list.items.push({ type: 'list_item', raw, task, checked, loose, text: item });
}
source = source.substring(list.raw.length);
for (const item of list.items) {
item.tokens = this._tokenize(item.text, [], links, false);
}
tokens.push(list);
continue;
}
match = this._htmlRegExp.exec(source);
if (match) {
source = source.substring(match[0].length);
tokens.push({ type: 'html', pre: (match[1] === 'pre' || match[1] === 'script' || match[1] === 'style'), text: match[0] });
continue;
}
if (top) {
match = this._defRegExp.exec(source);
if (match) {
source = source.substring(match[0].length);
match[3] = match[3] ? match[3].substring(1, match[3].length - 1) : match[3];
const tag = match[1].toLowerCase().replace(/\s+/g, ' ');
if (!links.has(tag)) {
links.set(tag, { href: match[2], title: match[3] });
}
continue;
}
}
match = this._tableRegExp.exec(source);
if (match) {
const header = this._splitCells(match[1].replace(/^ *| *\| *$/g, ''));
const align = match[2].replace(/^ *|\| *$/g, '').split(/ *\| */);
if (header.length === align.length) {
const cells = match[3] ? match[3].replace(/\n$/, '').split('\n') : [];
const token = { type: 'table', header, align, cells, raw: match[0] };
for (let i = 0; i < token.align.length; i++) {
if (/^ *-+: *$/.test(token.align[i])) {
token.align[i] = 'right';
} else if (/^ *:-+: *$/.test(token.align[i])) {
token.align[i] = 'center';
} else if (/^ *:-+ *$/.test(token.align[i])) {
token.align[i] = 'left';
} else {
token.align[i] = null;
}
}
token.cells = token.cells.map((cell) => this._splitCells(cell.replace(/^ *\| *| *\| *$/g, ''), token.header.length));
source = source.substring(token.raw.length);
tokens.push(token);
continue;
}
}
match = this._lheadingRegExp.exec(source);
if (match) {
source = source.substring(match[0].length);
tokens.push({ type: 'heading', depth: match[2].charAt(0) === '=' ? 1 : 2, text: match[1] });
continue;
}
if (top) {
match = this._paragraphRegExp.exec(source);
if (match) {
source = source.substring(match[0].length);
tokens.push({ type: 'paragraph', text: match[1].charAt(match[1].length - 1) === '\n' ? match[1].slice(0, -1) : match[1] });
continue;
}
}
match = this._textRegExp.exec(source);
if (match) {
source = source.substring(match[0].length);
const lastToken = tokens[tokens.length - 1];
if (lastToken && lastToken.type === 'text') {
lastToken.text += `\n${match[0]}`;
} else {
tokens.push({ type: 'text', text: match[0] });
}
continue;
}
throw new Error(`Unexpected '${source.charCodeAt(0)}'.`);
}
return tokens;
}
_tokenizeInline(source, links, inLink, inRawBlock, prevChar) {
const tokens = [];
let maskedSource = source;
if (links.size > 0) {
while (maskedSource) {
const match = this._reflinkSearchRegExp.exec(maskedSource);
if (match) {
if (links.has(match[0].slice(match[0].lastIndexOf('[') + 1, -1))) {
maskedSource = `${maskedSource.slice(0, match.index)}[${'a'.repeat(match[0].length - 2)}]${maskedSource.slice(this._reflinkSearchRegExp.lastIndex)}`;
}
continue;
}
break;
}
}
while (maskedSource) {
const match = this._blockSkipRegExp.exec(maskedSource);
if (match) {
maskedSource = `${maskedSource.slice(0, match.index)}[${'a'.repeat(match[0].length - 2)}]${maskedSource.slice(this._blockSkipRegExp.lastIndex)}`;
continue;
}
break;
}
while (source) {
let match = this._escapeRegExp.exec(source);
if (match) {
source = source.substring(match[0].length);
tokens.push({ type: 'escape', text: this._escape(match[1]) });
continue;
}
match = this._tagRegExp.exec(source);
if (match) {
source = source.substring(match[0].length);
if (!inLink && /^/i.test(match[0])) {
inLink = false;
}
if (!inRawBlock && /^<(pre|code|kbd|script)(\s|>)/i.test(match[0])) {
inRawBlock = true;
} else if (inRawBlock && /^<\/(pre|code|kbd|script)(\s|>)/i.test(match[0])) {
inRawBlock = false;
}
tokens.push({ type: 'html', raw: match[0], text: match[0] });
continue;
}
match = this._linkRegExp.exec(source);
if (match) {
let index = -1;
const [, , ref] = match;
if (ref.indexOf(')') !== -1) {
let level = 0;
for (let i = 0; i < ref.length; i++) {
switch (ref[i]) {
case '\\':
i++;
break;
case '(':
level++;
break;
case ')':
level--;
if (level < 0) {
index = i;
i = ref.length;
}
break;
default:
break;
}
}
}
if (index > -1) {
const length = (match[0].indexOf('!') === 0 ? 5 : 4) + match[1].length + index;
match[2] = match[2].substring(0, index);
match[0] = match[0].substring(0, length).trim();
match[3] = '';
}
const title = (match[3] ? match[3].slice(1, -1) : '').replace(this._escapesRegExp, '$1');
const href = match[2].trim().replace(/^<([\s\S]*)>$/, '$1').replace(this._escapesRegExp, '$1');
const token = this._outputLink(match, href, title);
source = source.substring(match[0].length);
if (token.type === 'link') {
token.tokens = this._tokenizeInline(token.text, links, true, inRawBlock, '');
}
tokens.push(token);
continue;
}
match = this._reflinkRegExp.exec(source) || this._nolinkRegExp.exec(source);
if (match) {
let link = (match[2] || match[1]).replace(/\s+/g, ' ');
link = links.get(link.toLowerCase());
if (!link || !link.href) {
const text = match[0].charAt(0);
source = source.substring(text.length);
tokens.push({ type: 'text', text });
} else {
source = source.substring(match[0].length);
const token = this._outputLink(match, link);
if (token.type === 'link') {
token.tokens = this._tokenizeInline(token.text, links, true, inRawBlock, '');
}
tokens.push(token);
}
continue;
}
match = this._strongStartRegExp.exec(source);
if (match && (!match[1] || (match[1] && (prevChar === '' || this._punctuationRegExp.exec(prevChar))))) {
const masked = maskedSource.slice(-1 * source.length);
const endReg = match[0] === '**' ? this._strongEndAstRegExp : this._strongEndUndRegExp;
endReg.lastIndex = 0;
let cap = '';
while ((match = endReg.exec(masked)) !== null) {
cap = this._strongMiddleRegExp.exec(masked.slice(0, match.index + 3));
if (cap) {
break;
}
}
if (cap) {
const text = source.substring(2, cap[0].length - 2);
source = source.substring(cap[0].length);
tokens.push({ type: 'strong', text, tokens: this._tokenizeInline(text, links, inLink, inRawBlock, '') });
continue;
}
}
match = this._emStartRegExp.exec(source);
if (match && (!match[1] || (match[1] && (prevChar === '' || this._punctuationRegExp.exec(prevChar))))) {
const masked = maskedSource.slice(-1 * source.length);
const endReg = match[0] === '*' ? this._emEndAstRegExp : this._emEndUndRegExp;
endReg.lastIndex = 0;
let cap = '';
while ((match = endReg.exec(masked)) !== null) {
cap = this._emMiddleRegExp.exec(masked.slice(0, match.index + 2));
if (cap) {
break;
}
}
if (cap) {
const text = source.slice(1, cap[0].length - 1);
source = source.substring(cap[0].length);
tokens.push({ type: 'em', text, tokens: this._tokenizeInline(text, links, inLink, inRawBlock, '') });
continue;
}
}
match = this._codespanRegExp.exec(source);
if (match) {
source = source.substring(match[0].length);
let content = match[2].replace(/\n/g, ' ');
if (/[^ ]/.test(content) && content.startsWith(' ') && content.endsWith(' ')) {
content = content.substring(1, content.length - 1);
}
tokens.push({ type: 'codespan', text: this._encode(content) });
continue;
}
match = this._brRegExp.exec(source);
if (match) {
source = source.substring(match[0].length);
tokens.push({ type: 'br' });
continue;
}
match = this._delRegExp.exec(source);
if (match) {
const [value, text] = match;
source = source.substring(value.length);
tokens.push({ type: 'del', text, tokens: this._tokenizeInline(text, links, inLink, inRawBlock, '') });
continue;
}
match = this._autolinkRegExp.exec(source);
if (match) {
source = source.substring(match[0].length);
const text = this._escape(match[1]);
const href = match[2] === '@' ? `mailto:${text}` : text;
tokens.push({ type: 'link', text, href, tokens: [{ type: 'text', raw: text, text }] });
continue;
}
if (!inLink) {
match = this._urlRegExp.exec(source);
if (match) {
const email = match[2] === '@';
let [value] = match;
if (!email) {
let prevCapZero = '';
do {
prevCapZero = value;
[value] = this._backpedalRegExp.exec(value);
} while (prevCapZero !== value);
}
const text = this._escape(value);
let href = text;
if (email) {
href = `mailto:${text}`;
} else if (text.startsWith('www.')) {
href = `http://${text}`;
}
source = source.substring(value.length);
tokens.push({ type: 'link', text, href, tokens: [{ type: 'text', text }] });
continue;
}
}
match = this._textspanRegExp.exec(source);
if (match) {
source = source.substring(match[0].length);
prevChar = match[0].slice(-1);
tokens.push({ type: 'text' , text: inRawBlock ? match[0] : this._escape(match[0]) });
continue;
}
throw new Error(`Unexpected '${source.charCodeAt(0)}'.`);
}
return tokens;
}
_tokenizeBlock(tokens, links) {
for (const token of tokens) {
switch (token.type) {
case 'paragraph':
case 'text':
case 'heading': {
token.tokens = this._tokenizeInline(token.text, links, false, false, '');
break;
}
case 'table': {
token.tokens = {};
token.tokens.header = token.header.map((header) => this._tokenizeInline(header, links, false, false, ''));
token.tokens.cells = token.cells.map((cell) => cell.map((row) => this._tokenizeInline(row, links, false, false, '')));
break;
}
case 'blockquote': {
this._tokenizeBlock(token.tokens, links);
break;
}
case 'list': {
for (const item of token.items) {
this._tokenizeBlock(item.tokens, links);
}
break;
}
default: {
break;
}
}
}
}
_render(tokens, top) {
let html = '';
while (tokens.length > 0) {
const token = tokens.shift();
switch (token.type) {
case 'space': {
continue;
}
case 'hr': {
html += '${token.escaped ? code : this._encode(code)}\n`;
continue;
}
case 'table': {
let header = '';
let cell = '';
for (let j = 0; j < token.header.length; j++) {
const content = this._renderInline(token.tokens.header[j]);
const align = token.align[j];
cell += `\n${this._render(token.tokens, true)}\n`; continue; } case 'list': { const ordered = token.ordered; const start = token.start; const loose = token.loose; let body = ''; for (const item of token.items) { let itemBody = ''; if (item.task) { const checkbox = ` `; if (loose) { if (item.tokens.length > 0 && item.tokens[0].type === 'text') { item.tokens[0].text = `${checkbox} ${item.tokens[0].text}`; if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === 'text') { item.tokens[0].tokens[0].text = `${checkbox} ${item.tokens[0].tokens[0].text}`; } } else { item.tokens.unshift({ type: 'text', text: checkbox }); } } else { itemBody += checkbox; } } itemBody += this._render(item.tokens, loose); body += `
${this._renderInline(token.tokens)}
\n`; continue; } case 'text': { html += top ? '' : ''; html += token.tokens ? this._renderInline(token.tokens) : token.text; while (tokens.length > 0 && tokens[0].type === 'text') { const token = tokens.shift(); html += `\n${token.tokens ? this._renderInline(token.tokens) : token.text}`; } html += top ? '
\n' : ''; continue; } default: { throw new Error(`Unexpected token type '${token.type}'.`); } } } return html; } _renderInline(tokens) { let html = ''; for (const token of tokens) { switch (token.type) { case 'escape': case 'html': case 'text': { html += token.text; break; } case 'link': { const text = this._renderInline(token.tokens); html += `${text}`; break; } case 'image': { html += `${token.text}`;
break;
}
case 'br': {
html += '