Add tensor denotation edit UI, update datastore, show docstring in HoverCard
This commit is contained in:
Родитель
09f12f4298
Коммит
fb05143137
|
@ -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') {
|
||||
|
|
Загрузка…
Ссылка в новой задаче