@webwriter/neural-network
Version:
Deep learning visualization for feed-forward networks with custom datasets, training and prediction.
614 lines (534 loc) • 21.2 kB
text/typescript
import { LitElementWw } from '@webwriter/lit'
import { CSSResult, TemplateResult, html, css, PropertyDeclarations, PropertyValues } from 'lit'
import { customElement, property /* , query */, state } from 'lit/decorators.js'
import { ContextRoot, provide } from '@lit/context'
import '@shoelace-style/shoelace/dist/themes/light.css'
import '@shoelace-style/shoelace/dist/themes/dark.css'
import { globalStyles } from '@/global_styles'
import { ConfigurationController } from '@/controllers/configuration_controller'
import type { SetupStatus } from '@/types/setup_status'
import { setupStatusContext } from '@/contexts/setup_status_context'
import { SetupController } from '@/controllers/setup_controller'
import { SetupUtils } from '@/utils/setup_utils'
import { editableContext } from '@/contexts/editable_context'
import type { Settings } from '@/types/settings'
import { settingsContext } from '@/contexts/settings_context'
import { SettingsController } from '@/controllers/settings_controller'
import { SettingsUtils } from '@/utils/settings_utils'
import type { QAndAEntry } from '@/types/q_and_a_entry'
import { qAndAContext } from '@/contexts/q_and_a_context'
import { QAndAController } from '@/controllers/q_and_a_controller'
import { QAndAUtils } from '@/utils/q_and_a_utils'
import type { CCanvas } from '@/components/canvas'
import { canvasContext } from '@/contexts/canvas_context'
import type { CLayerConf } from '@/types/c_layer_conf'
import type { CLayerConnectionConf } from '@/types/c_layer_connection_conf'
import { CNetwork } from '@/components/network/network'
import { networkContext } from '@/contexts/network_context'
import { layerConfsContext } from '@/contexts/layer_confs_context'
import { layerConnectionConfsContext } from '@/contexts/layer_con_confs_context'
import { NetworkController } from '@/controllers/network_controller'
import type { DataSet } from '@/types/data_set'
import { dataSetContext } from '@/contexts/data_set_context'
import { availableDataSetsContext } from '@/contexts/available_data_sets_context'
import { DataSetController } from '@/controllers/data_set_controller'
import { DataSetUtils } from '@/utils/data_set_utils'
import type { TrainOptions } from '@/types/train_options'
import type { ModelConf } from '@/types/model_conf'
import { trainOptionsContext } from '@/contexts/train_options_context'
import { modelConfContext } from '@/contexts/model_conf_context'
import { ModelController } from '@/controllers/model_controller'
import { ModelUtils } from '@/utils/model_utils'
import type { Selected } from '@/types/selected'
import type { SelectedEle } from '@/types/selected_ele'
import { selectedContext } from '@/contexts/selected_context'
import { selectedEleContext } from '@/contexts/selected_ele_context'
import { SelectionController } from '@/controllers/selection_controller'
import { panelContext } from '@/contexts/panels_context'
import { PanelController } from '@/controllers/panel_controller'
/* import { AlertController } from '@/controllers/alert_controller'
import { AlertUtils } from '@/utils/alert_utils'*/
import type { Theme } from '@/types/theme'
import { themeContext } from '@/contexts/theme_context'
import { ThemeController } from '@/controllers/theme_controller'
import { ThemeUtils } from '@/utils/theme_utils'
import { CCanvasArea } from '@/components/canvas_area'
import { MenuArea } from '@/components/menu_area'
import { ThemeSwitch } from './components/theme_switch'
import { ContextProvider } from '@lit/context'
import '@webcomponents/scoped-custom-element-registry';
import { styleMap } from 'lit/directives/style-map.js'
import { localized, msg } from '@lit/localize'
import LOCALIZE from "../localization/generated";
/**
* @summary Deep learning visualization for feed-forward networks with custom datasets, training and prediction.
*
* @tag webwriter-neural-network
* @tagname webwriter-neural-network
*
* @attr {boolean} [editable=false] - Enables authoring/editing features in child components. Reflected to the "editable" attribute.
* @attr {Settings} settings - Current application settings. Provide as a property for non-string values.
* @attr {QAndAEntry[]} qAndA - Static help content.
* @attr {CLayerConf[]} layerConfs - Network layer configurations.
* @attr {CLayerConnectionConf[]} layerConnectionConfs - Layer connection configurations.
* @attr {DataSet} dataSet - Active dataset.
* @attr {DataSet[]} availableDataSets - List of available datasets.
* @attr {TrainOptions} trainOptions - Training options.
*
* @prop {SetupStatus} setupStatus - Setup state of the widget.
* @prop {CCanvas} canvas - Canvas instance created by the canvas-area.
* @prop {CNetwork} network - Network instance.
* @prop {ModelConf} modelConf - Current model configuration.
* @prop {Selected} selected - Current multi-selection state.
* @prop {SelectedEle} selectedEle - Current single selected element.
* @prop {boolean} panel - Whether the right panel is open.
* @prop {Theme} theme - Active theme object with style string.
*
* @cssproperty --sl-color-neutral-0 - Host background color (forwarded from Shoelace).
* @cssproperty --sl-color-neutral-50 - Divider color (forwarded from Shoelace).
*/
export class NeuralNetwork extends LitElementWw {
/** @internal Localization bundle used by @lit/localize. */
public localize = LOCALIZE;
/**
* Declares reactive properties and attribute reflection.
*/
static properties: PropertyDeclarations = {
setupStatus: { attribute: false },
editable: { attribute: true, type: Boolean, reflect: true },
settings: { attribute: true, type: Object, reflect: true },
qAndA: { attribute: true, type: Object, reflect: true },
canvas: {attribute: false},
network: {attribute: false},
layerConfs: { attribute: true, type: Array, reflect: true },
layerConnectionConfs: { attribute: true, type: Array, reflect: true },
dataSet: { attribute: true, type: Object, reflect: true },
availableDataSets: { attribute: true, type: Array, reflect: true },
trainOptions: { attribute: true, type: Object, reflect: true },
modelConf: { attribute: false },
selected: { attribute: false },
selectedEle: { attribute: false },
panel: { attribute: false },
theme: { attribute: false }
}
/**
* Creates context providers for all data channels and initializes defaults via utility modules.
* @internal
*/
constructor() {
super()
this.setupStatusProvider = new ContextProvider(this, {context: setupStatusContext, initialValue: SetupUtils.defaultSetupStatus})
this.editableProvider = new ContextProvider(this, {context: editableContext, initialValue: false})
this.settingsProvider = new ContextProvider(this, {context: settingsContext, initialValue: JSON.parse(JSON.stringify(SettingsUtils.defaultSettings))})
this.qAndAProvider = new ContextProvider(this, {context: qAndAContext, initialValue: [...QAndAUtils.defaultQAndA]})
this.canvasProvider = new ContextProvider(this, {context: canvasContext})
this.networkProvider = new ContextProvider(this, {context: networkContext})
this.layerConfsProvider = new ContextProvider(this, {context: layerConfsContext, initialValue: []})
this.layerConnectionConfsProvider = new ContextProvider(this, {context: layerConnectionConfsContext, initialValue: []})
this.dataSetProvider = new ContextProvider(this, {context: dataSetContext, initialValue: DataSetUtils.defaultDataSet})
this.availableDataSetsProvider = new ContextProvider(this, {context: availableDataSetsContext, initialValue: DataSetUtils.defaultAvailableDataSets})
this.trainOptionsProvider = new ContextProvider(this, {context: trainOptionsContext, initialValue: <TrainOptions>(JSON.parse(JSON.stringify(ModelUtils.defaultTrainOptions)))})
this.modelConfProvider = new ContextProvider(this, {context: modelConfContext, initialValue: <ModelConf>(JSON.parse(JSON.stringify(ModelUtils.defaultModelConf)))})
this.selectedProvider = new ContextProvider(this, {context: selectedContext, initialValue: {}})
this.selectedEleProvider = new ContextProvider(this, {context: selectedEleContext})
this.panelProvider = new ContextProvider(this, {context: panelContext})
this.themeProvider = new ContextProvider(this, {context: themeContext, initialValue: ThemeUtils.lightTheme})
}
protected setupStatusProvider: ContextProvider<any, NeuralNetwork>
protected editableProvider: ContextProvider<any, NeuralNetwork>
protected settingsProvider: ContextProvider<any, NeuralNetwork>
protected qAndAProvider: ContextProvider<any, NeuralNetwork>
protected canvasProvider: ContextProvider<any, NeuralNetwork>
protected networkProvider: ContextProvider<any, NeuralNetwork>
protected layerConfsProvider: ContextProvider<any, NeuralNetwork>
protected layerConnectionConfsProvider: ContextProvider<any, NeuralNetwork>
protected dataSetProvider: ContextProvider<any, NeuralNetwork>
protected availableDataSetsProvider: ContextProvider<any, NeuralNetwork>
protected trainOptionsProvider: ContextProvider<any, NeuralNetwork>
protected modelConfProvider: ContextProvider<any, NeuralNetwork>
protected selectedProvider: ContextProvider<any, NeuralNetwork>
protected selectedEleProvider: ContextProvider<any, NeuralNetwork>
protected panelProvider: ContextProvider<any, NeuralNetwork>
protected themeProvider: ContextProvider<any, NeuralNetwork>
/**
* Lit lifecycle hook. Attaches a ContextRoot to the document body to enable
* using context outside the component tree when necessary.
* @internal
*/
connectedCallback(): void {
super.connectedCallback()
const root = new ContextRoot();
root.attach(document.body);
}
/**
* Whether the editor is in fullscreen mode.
* @internal
*/
private get isFullscreen(): boolean {
return this.ownerDocument.fullscreenElement === this;
}
/**
* Toggles fullscreen mode using the Fullscreen API and requests a re-render afterwards.
* @internal
*/
private async _onFullscreenToggle() {
if (this.isFullscreen) {
await this.ownerDocument.exitFullscreen();
this.style.height = "500px"
this.style.width = "min(100%,796px)"
this.requestUpdate()
} else {
try {
await this.requestFullscreen();
this.style.height = "100%"
this.style.width = "100%"
this.requestUpdate()
} catch (error) {
console.error(msg("Failed to enter fullscreen mode."));
}
}
}
/**
* Lit lifecycle hook: invoked after the component's DOM is first rendered.
* Adjusts host dimensions based on its bounding client rect to account for borders.
* @internal
*/
protected firstUpdated(_changedProperties: PropertyValues): void {
super.firstUpdated(_changedProperties)
setTimeout(() => {
const dim: DOMRect = this.getBoundingClientRect()
this.style.height = Math.max(dim.height - 4, 500) +"px"
this.style.width = "min(100%," + (dim.width - 4) + "px)"
});
}
/**
* Scoped element registry for child components used by this widget.
*/
protected static scopedElements = {
"canvas-area": CCanvasArea,
"menu-area": MenuArea,
"c-network": CNetwork,
"theme-switch": ThemeSwitch
}
// DATA PROVIDERS AND CONTROLLERS - - - - - - - - - - - - - - - - - - - - - -
/** @internal Global configuration controller for the widget. */
private configurationController = new ConfigurationController(this)
// -> SETUP STATUS -----------------------------------------------------------
/**
* Setup status of the widget.
*/
get setupStatus(): SetupStatus {
return this.setupStatusProvider.value
}
set setupStatus(value: SetupStatus) {
this.setupStatusProvider.setValue(value)
this.requestUpdate("setupStatus")
}
/** @internal Controller handling setup lifecycle and transitions. */
private setupController = new SetupController(this)
// -> EDITABLE ---------------------------------------------------------------
/**
* Whether editing is enabled.
*/
get editable(): boolean {
return this.editableProvider.value
}
set editable(value: boolean) {
this.editableProvider.setValue(value)
this.requestUpdate("editable")
}
// -> SETTINGS ---------------------------------------------------------------
/**
* Application settings.
*/
get settings(): Settings {
return this.settingsProvider.value
}
set settings(value: Settings) {
this.settingsProvider.setValue(value)
this.requestUpdate("settings")
}
/** @internal Controller for reading/updating settings. */
private settingsController = new SettingsController(this)
// -> HELP -------------------------------------------------------------------
/**
* Help/Q&A content.
*/
get qAndA(): QAndAEntry[] {
return this.qAndAProvider.value
}
set qAndA(value: QAndAEntry[]) {
this.qAndAProvider.setValue(value)
this.requestUpdate("qAndA")
}
/** @internal Controller for maintaining Q&A content. */
private qAndAController = new QAndAController(this)
// -> CANVAS -----------------------------------------------------------------
/**
* Canvas instance created by the canvas-area child component.
*/
get canvas(): CCanvas | undefined {
return this.canvasProvider.value
}
set canvas(value: CCanvas | undefined) {
this.canvasProvider.setValue(value)
this.requestUpdate("canvas")
}
// -> NETWORK ----------------------------------------------------------------
/**
* Network instance used for neural network structure visualization.
*/
get network(): CNetwork | undefined {
return this.networkProvider.value
}
set network(value: CNetwork | undefined) {
this.networkProvider.setValue(value)
this.requestUpdate("network")
}
/**
* Layer configuration list.
*/
get layerConfs(): CLayerConf[] {
return this.layerConfsProvider.value
}
set layerConfs(value: CLayerConf[]) {
this.layerConfsProvider.setValue(value)
this.requestUpdate("layerConfs")
}
/**
* Layer connection configuration list between layers.
*/
get layerConnectionConfs(): CLayerConnectionConf[] {
return this.layerConnectionConfsProvider.value
}
set layerConnectionConfs(value: CLayerConnectionConf[]) {
this.layerConnectionConfsProvider.setValue(value)
this.requestUpdate("layerConnectionConfs")
}
/** @internal Controller handling network operations and mutations. */
private networkController = new NetworkController(this)
// -> DATA SET ---------------------------------------------------------------
/**
* Active dataset.
*/
get dataSet(): DataSet {
return this.dataSetProvider.value
}
set dataSet(value: DataSet) {
this.dataSetProvider.setValue(value)
this.requestUpdate("dataSet")
}
/**
* Available datasets.
*/
get availableDataSets(): DataSet[] {
return this.availableDataSetsProvider.value
}
set availableDataSets(value: DataSet[]) {
this.availableDataSetsProvider.setValue(value)
this.requestUpdate("availableDataSets")
}
/** @internal Controller for dataset loading/validation and selection. */
private dataSetController = new DataSetController(this)
// -> MODEL ------------------------------------------------------------------
/**
* Training options.
*/
get trainOptions(): TrainOptions {
return this.trainOptionsProvider.value
}
set trainOptions(value: TrainOptions) {
this.trainOptionsProvider.setValue(value)
this.requestUpdate("trainOptions")
}
/**
* Model configuration.
*/
get modelConf(): ModelConf {
return this.modelConfProvider.value
}
set modelConf(value: ModelConf) {
this.modelConfProvider.setValue(value)
this.requestUpdate("modelConf")
}
/** @internal Container reference for displaying training metrics. */
private trainMetricsContainer: HTMLDivElement
/** @internal Controller for model lifecycle and training orchestration. */
private modelController = new ModelController(this)
// -> SELECTED ---------------------------------------------------------------
/**
* Current selection state.
*/
get selected(): Selected {
return this.selectedProvider.value
}
set selected(value: Selected) {
this.selectedProvider.setValue(value)
this.requestUpdate("selected")
}
/**
* Currently selected element.
*/
get selectedEle(): SelectedEle {
return this.selectedEleProvider.value
}
set selectedEle(value: SelectedEle) {
this.selectedEleProvider.setValue(value)
this.requestUpdate("selectedEle")
}
/** @internal Controller handling selection logic and events. */
private selectionController = new SelectionController(this)
// -> PANELS -----------------------------------------------------------------
/**
* Whether the right panel is shown.
*/
get panel(): boolean {
return this.panelProvider.value
}
set panel(value: boolean) {
this.panelProvider.setValue(value)
this.requestUpdate("panel")
}
/** @internal Controller for panel state and interactions. */
private panelController = new PanelController(this)
// -> THEME ------------------------------------------------------------------
/**
* Active theme object.
*/
get theme(): Theme {
return this.themeProvider.value
}
set theme(value: Theme) {
this.themeProvider.setValue(value)
this.requestUpdate("theme")
}
/** @internal Controller for theme switching and persistence. */
private themeController = new ThemeController(this)
// STYLES --------------------------------------------------------------------
/**
* Styles for the host layout, canvas/menu areas, divider, and theme switch.
*/
static styles: CSSResult[] = [
globalStyles,
css`
:host {
display: flex!important;
flex-direction: row;
overflow: hidden;
background-color: var(--sl-color-neutral-0);
height: 100%;
}
:host.embedded {
/* min-height: 400px;
height: 100%; */
display: flex!important;
flex-direction: row;
overflow: hidden;
background-color: var(--sl-color-neutral-0);
}
#loadingPage {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: var(--sl-color-neutral-0);
}
#loadingDiv {
position: absolute;
left: 50%;
top: 50%;
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
align-items: center;
}
canvas-area {
width: calc(100% - 435px);
height: 100%;
}
canvas-area.right-collapsed {
width: 100%;
}
menu-area {
width: 100%;
}
menu-area.right-collapsed {
width: 0;
}
#divider {
position: absolute;
right: 435px;
width: 2px;
top: 10px;
bottom: 10px;
background-color: var(--sl-color-neutral-50);
}
theme-switch {
position: absolute;
bottom: 10px;
left: 10px;
}
/* .sl-toast-stack {
top: 300 !important;
width: 50rem !important;
} */
`,
]
// RENDER --------------------------------------------------------------------
/**
* Rendering
*
* @returns An array of TemplateResult parts composing the UI.
*/
render(): TemplateResult<1>[] {
const renderedHTML: TemplateResult<1>[] = []
/* renderedHTML.push(html`<div class="sl-toast-stack"></div>`) */
renderedHTML.push(
html`<style>
${(this.theme as any).styles}
:host{
border-width: 2px;
border-style: solid;
border-radius: 5px;
border-color: #6a6a6a;
}
</style>`
)
renderedHTML.push(html` <canvas-area
class="${!this.panel ? 'right-collapsed' : ''}"
=${()=>{this.dispatchEvent(new Event("focus"))}}
-created="${(e: CustomEvent<CCanvas>) => {
this.canvas = e.detail
}}"
.fullscreen=${this.isFullscreen}
-fullscreen="${this._onFullscreenToggle}"
>
</canvas-area>`)
if ((this.setupStatus as any).loading) {
renderedHTML.push(html`
<div id="loadingPage">
<div id="loadingDiv">
<h1>${msg("Loading")}</h1>
<sl-spinner style="font-size: 3rem;"></sl-spinner>
</div>
</div>
`)
} else {
renderedHTML.push(html`
<div id="divider" class="${!this.panel ? 'hidden' : ''}"></div>
<menu-area
class="${!this.panel ? 'right-collapsed' : ''}"
-train-metrics-container="${(e: CustomEvent<HTMLDivElement>) =>
this.modelController.setTrainMetricsContainer(e.detail)}"
></menu-area>
<c-network></c-network>
`)
}
// renderedHTML.push(html`<theme-switch></theme-switch>`)
return renderedHTML
}
}
customElements.define("webwriter-neural-network", NeuralNetwork)