Add tensor denotation edit UI, update datastore, show docstring in HoverCard

This commit is contained in:
Tiago Koji Castro Shibata 2018-08-01 15:01:56 -07:00
Родитель 09f12f4298
Коммит fb05143137
10 изменённых файлов: 167 добавлений и 58 удалений

Просмотреть файл

@ -8,8 +8,8 @@
cursor: pointer;
text-align: left;
border: 1px lightgray;
border-style: solid none none;
padding: 5px;
border-style: solid none;
padding: 5px 5px 10px;
}
.Content {

Просмотреть файл

@ -23,6 +23,7 @@ export default class Collapsible extends React.Component<IComponentProperties, I
if (!this.state.visible) {
style.maxHeight = '0px';
style.visibility = 'hidden';
style.padding = '0px';
}
return (
<div>

Просмотреть файл

@ -26,7 +26,7 @@ interface IComponentState {
keyErrors: { [key: string]: string },
}
class KeyValueEditorComponent extends React.Component<IComponentProperties, IComponentState> {
class KeyValueEditor extends React.Component<IComponentProperties, IComponentState> {
constructor(props: IComponentProperties) {
super(props);
this.state = {
@ -211,5 +211,4 @@ const mapDispatchToProps = (dispatch: any, ownProps: IComponentProperties) => ({
updateKeyValueObject: (keyValueObject: { [key: string]: string }) => dispatch(ownProps.actionCreator!(keyValueObject)),
})
const KeyValueEditor = connect(mapStateToProps, mapDispatchToProps)(KeyValueEditorComponent as any);
export default KeyValueEditor;
export default connect(mapStateToProps, mapDispatchToProps)(KeyValueEditor as any);

Просмотреть файл

@ -1,9 +1,14 @@
import { IAction, SET_METADATA_PROPS } from '../actions';
import { IAction, SET_INPUTS, SET_METADATA_PROPS } from '../actions';
import { ModelProtoSingleton } from './modelProto';
export const protoMiddleware = (store: any) => (next: (action: IAction) => any) => (action: IAction) => {
if (action.type === SET_METADATA_PROPS) {
ModelProtoSingleton.setMetadata(action.metadataProps);
switch (action.type) {
case SET_INPUTS:
ModelProtoSingleton.setInputs(action.inputs);
return next(action);
case SET_METADATA_PROPS:
ModelProtoSingleton.setMetadata(action.metadataProps);
return next(action);
}
return next(action);
}

Просмотреть файл

@ -3,6 +3,13 @@ import { Proto } from './proto';
class ModelProto extends Proto {
public setInputs(inputs: { [key: string]: any }) {
if (!Proto.getOnnx() || !this.proto) {
return;
}
this.proto.graph.input = inputs;
}
public setMetadata(metadata: IMetadataProps) {
if (!Proto.getOnnx() || !this.proto) {
return;

Просмотреть файл

@ -9,8 +9,8 @@ export interface IProperties {
export default interface IState {
inputs: { [key: string]: any },
metadataProps: IMetadataProps,
modelInputs: { [key: string]: any },
modelOutputs: { [key: string]: any },
modelInputs: string[],
modelOutputs: string[],
nodes: { [key: string]: any },
outputs: { [key: string]: any },
properties: IProperties,

Просмотреть файл

@ -1,3 +1,5 @@
import { ComboBox, IComboBoxOption } from 'office-ui-fabric-react/lib/ComboBox';
import { ExpandingCardMode, HoverCard, IExpandingCardProps } from 'office-ui-fabric-react/lib/HoverCard';
import { Label } from 'office-ui-fabric-react/lib/Label';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import * as React from 'react';
@ -5,14 +7,20 @@ import { connect } from 'react-redux';
import Collapsible from '../../components/Collapsible';
import Resizable from '../../components/Resizable';
import { setInputs } from '../../datastore/actionCreators';
import IState from '../../datastore/state';
import './Panel.css';
interface IComponentProperties {
// Redux properties
inputs: { [key: string]: any },
modelInputs: string[];
modelOutputs: string[];
nodes: { [key: string]: any },
outputs: { [key: string]: any },
selectedNode: string,
setInputs: typeof setInputs,
}
class LeftPanel extends React.Component<IComponentProperties, {}> {
@ -25,54 +33,111 @@ class LeftPanel extends React.Component<IComponentProperties, {}> {
}
private getContent() {
const node = this.props.nodes[this.props.selectedNode];
const inputsForm = [];
for (const input of node.input) {
inputsForm.push(
<div key={input}>
<Label className='TensorName'>{input}</Label>
<span className='Shape'>
<TextField inputMode='numeric' type='number' placeholder='N' className='ShapeTextField' />
<TextField inputMode='numeric' type='number' placeholder='C' className='ShapeTextField' />
<TextField inputMode='numeric' type='number' placeholder='H' className='ShapeTextField' />
<TextField inputMode='numeric' type='number' placeholder='W' className='ShapeTextField' />
</span>
</div>
);
}
const outputsForm = [];
for (const output of node.output) {
outputsForm.push(
<div key={output}>
<Label className='TensorName'>{output}</Label>
<span className='Shape'>
<TextField inputMode='numeric' type='number' placeholder='N' className='ShapeTextField' />
<TextField inputMode='numeric' type='number' placeholder='C' className='ShapeTextField' />
<TextField inputMode='numeric' type='number' placeholder='H' className='ShapeTextField' />
<TextField inputMode='numeric' type='number' placeholder='W' className='ShapeTextField' />
</span>
</div>
);
const modelPropertiesSelected = this.props.selectedNode === 'Model Properties';
let input: any[];
let output: any[];
if (modelPropertiesSelected) {
input = this.props.modelInputs;
output = this.props.modelOutputs;
} else {
({ input, output } = this.props.nodes[this.props.selectedNode]);
}
const inputsForm = this.buildConnectionList(input);
const outputsForm = this.buildConnectionList(output);
return (
<div>
<Label className='PanelName'>{`Node: ${this.props.selectedNode || ''}`}</Label>
<Label className='PanelName'>{this.props.selectedNode ? (`${modelPropertiesSelected ? '' : 'Node: '}${this.props.selectedNode}`) : ''}</Label>
<div className='Panel'>
<Collapsible label='Tensor shapes'>
<Label>Inputs</Label>
<Collapsible label='Inputs'>
{inputsForm}
<Label>Outputs</Label>
</Collapsible>
<Collapsible label='Outputs'>
{outputsForm}
</Collapsible>
</div>
</div>
);
}
private buildConnectionList = (connections: any[]) => {
const denotationOptions = ['', 'IMAGE', 'AUDIO', 'TEXT', 'TENSOR'].map((key: string) => ({ key, text: key }));
return connections.reduce((acc: any[], x: any) => {
const valueInfoProto = this.props.inputs[x] || this.props.outputs[x];
if (!valueInfoProto) {
acc.push(
<div key={x}>
<Label className='TensorName' disabled={true}>{x}</Label>
</div>
);
} else {
const keyChangedCallback = (option?: IComboBoxOption, index?: number, value?: string) => {
const nextInputs = { ...this.props.inputs };
nextInputs[x].type.denotation = value || option!.text;
this.props.setInputs(nextInputs);
};
const onnxType = Object.keys(valueInfoProto.type).find((t: string) => ['tensorType', 'sequenceType', 'mapType'].includes(t)) || 'unknownType';
const type = onnxType.slice(0, -4);
let tensorName = (
<div className={valueInfoProto.docString ? 'TensorDocumentationHover' : 'TensorName'}>
<b>{x}</b><span>{` (type: ${type})`}</span>
</div>
);
if (valueInfoProto.docString) {
const expandingCardProps: IExpandingCardProps = {
compactCardHeight: 100,
mode: ExpandingCardMode.compact,
onRenderCompactCard: (item: any): JSX.Element => (
<p className='DocString'>{valueInfoProto.docString}</p>
),
};
tensorName = (
<HoverCard expandingCardProps={expandingCardProps} instantOpenOnClick={true}>
{tensorName}
</HoverCard>
);
}
acc.push(
<div key={x}>
{tensorName}
<div className='TensorTypeDenotationDiv'>
<Label className='TensorTypeDenotationLabel'>Type denotation</Label>
<ComboBox
className='TensorTypeDenotation'
placeholder='Type denotation'
allowFreeform={true}
text={valueInfoProto.type.denotation}
options={denotationOptions}
disabled={!this.props.modelInputs.includes(x)}
onChanged={keyChangedCallback}
/>
</div>
<span className='Shape'>
<TextField inputMode='numeric' type='number' placeholder='N' className='ShapeTextField' />
<TextField inputMode='numeric' type='number' placeholder='C' className='ShapeTextField' />
<TextField inputMode='numeric' type='number' placeholder='H' className='ShapeTextField' />
<TextField inputMode='numeric' type='number' placeholder='W' className='ShapeTextField' />
</span>
</div>
);
}
return acc;
}, []);
}
}
const mapStateToProps = (state: IState) => ({
nodes: state.nodes,
selectedNode: state.selectedNode,
inputs: state.inputs,
modelInputs: state.modelInputs,
modelOutputs: state.modelOutputs,
nodes: state.nodes,
outputs: state.outputs,
selectedNode: state.selectedNode,
})
export default connect(mapStateToProps)(LeftPanel);
const mapDispatchToProps = {
setInputs,
};
export default connect(mapStateToProps, mapDispatchToProps)(LeftPanel);

Просмотреть файл

@ -13,9 +13,33 @@
display: inline-block;
}
.TensorName {
display: inline;
margin: 10px;
.TensorName, .TensorDocumentationHover {
display: block;
margin: 5px 5px;
}
.TensorDocumentationHover {
cursor: pointer;
}
.TensorTypeDenotationDiv {
display: flex;
}
.TensorTypeDenotationLabel {
margin: 3px 5px;
}
.TensorTypeDenotation {
flex: 1;
}
.DocString {
text-align: center;
vertical-align: middle;
height: 100%;
margin: 20px;
}
.Shape {

Просмотреть файл

@ -19,8 +19,9 @@ interface IComponentProperties {
class RightPanel extends React.Component<IComponentProperties, {}> {
public render() {
if (this.props.nodes === null) {
if (this.props.metadataProps !== undefined && !this.props.nodes) {
return (
// TODO Make it a button to navigate to the Convert tab
<Label className='FormatIsNotOnnx'>To support editing, convert the model to ONNX first.</Label>
);
}

Просмотреть файл

@ -17,6 +17,7 @@ interface IComponentProperties {
file?: File,
// Redux properties
nodes: { [key: string]: any },
setInputs: typeof setInputs,
setMetadataProps: typeof setMetadataProps,
setModelInputs: typeof setModelInputs,
@ -147,6 +148,14 @@ class NetronComponent extends React.Component<IComponentProperties, IComponentSt
}, {});
}
private valueListToObject(values: any) {
return values.reduce((acc: { [key: string]: any }, x: any) => {
acc[x.name] = x;
delete x.name;
return acc;
}, {});
}
private onNetronInitialized = () => {
// Reset document overflow property
document.documentElement.style.overflow = 'initial';
@ -180,7 +189,7 @@ class NetronComponent extends React.Component<IComponentProperties, IComponentSt
// FIXME What to do when model has multiple graphs?
const graph = model.graphs[0];
if (graph.constructor.name === 'OnnxGraph') {
const getNames = (list: any[]) => list.reduce((acc: string[], x: any) => {
const getNames = (list: any[]): string[] => list.reduce((acc: string[], x: any) => {
acc.push(x.name);
return acc;
}, []);
@ -188,14 +197,9 @@ class NetronComponent extends React.Component<IComponentProperties, IComponentSt
const outputs = getNames(graph.outputs);
this.props.setModelInputs(inputs);
this.props.setModelOutputs(outputs);
this.props.setInputs(inputs);
this.props.setOutputs(outputs);
const nodes = {};
for (const node of proto.graph.node) {
nodes[node.name] = node;
delete nodes[node.name].name;
}
this.props.setNodes(nodes);
this.props.setInputs(this.valueListToObject(proto.graph.input));
this.props.setOutputs(this.valueListToObject(proto.graph.output));
this.props.setNodes(this.valueListToObject(proto.graph.node));
this.props.setMetadataProps(this.propsToObject(model._metadataProps));
this.props.setProperties(this.propsToObject(model.properties));
} else {
@ -210,6 +214,9 @@ class NetronComponent extends React.Component<IComponentProperties, IComponentSt
};
private openPanel = (content: any, title: string, width?: number) => {
if (!this.props.nodes) {
return;
}
if (title === 'Node Properties') {
this.props.setSelectedNode(content[1].innerText);
} else if (title === 'Model Properties') {