-
Notifications
You must be signed in to change notification settings - Fork 480
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[AiLab] Model Manager Dialog and autogenerated design elements and code #38664
Changes from all commits
5af4d1e
4ed5c30
cd853b3
a018f1c
1a196bf
fe30108
74a8b96
7360835
c2b38fc
1b931ab
5387166
8ee19f7
a4ad3dc
a693b39
db7ce74
14a6519
568be9e
a92018d
26a9139
c561c6b
4425ebc
05f8cca
4354b6c
f2f07e5
6fd6500
72a2943
57878c8
2348784
30d1396
7b91f3b
52cf24b
15f5245
bf54c02
c313d29
90a6371
5a422f1
5144b3d
c2c4983
96f869c
d2e4a97
3827a44
bea44e7
66b4898
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import $ from 'jquery'; | ||
import designMode from './designMode'; | ||
|
||
function generateCodeDesignElements(modelId, modelData) { | ||
var x = 20; | ||
var y = 20; | ||
var SPACER_PIXELS = 20; | ||
designMode.onInsertEvent(`var testValues = {};`); | ||
modelData.selectedFeatures.forEach(feature => { | ||
y = y + SPACER_PIXELS; | ||
var label = designMode.createElement('LABEL', x, y); | ||
label.textContent = feature + ':'; | ||
label.id = 'design_' + feature + '_label'; | ||
label.style.width = '300px'; | ||
y = y + SPACER_PIXELS; | ||
if (Object.keys(modelData.featureNumberKey).includes(feature)) { | ||
var selectId = feature + '_dropdown'; | ||
var select = designMode.createElement('DROPDOWN', x, y); | ||
select.id = 'design_' + selectId; | ||
// App Lab automatically addss "option 1" and "option 2", remove them. | ||
select.options.remove(0); | ||
select.options.remove(0); | ||
Object.keys(modelData.featureNumberKey[feature]).forEach(option => { | ||
var optionElement = document.createElement('option'); | ||
optionElement.text = option; | ||
select.options.add(optionElement); | ||
}); | ||
} else { | ||
var input = designMode.createElement('TEXT_INPUT'); | ||
input.id = 'design_' + feature + '_input'; | ||
} | ||
var addFeature = `testValues.${feature} = getText("${selectId}");`; | ||
designMode.onInsertEvent(addFeature); | ||
}); | ||
y = y + 2 * SPACER_PIXELS; | ||
var label = designMode.createElement('LABEL', x, y); | ||
label.textContent = modelData.labelColumn; | ||
// TODO: this could be problematic if the name isn't formatted appropriately | ||
label.id = 'design_' + modelData.name + '_label'; | ||
label.style.width = '300px'; | ||
y = y + SPACER_PIXELS; | ||
var predictionId = modelData.name + '_prediction'; | ||
var prediction = designMode.createElement('TEXT_INPUT', x, y); | ||
prediction.id = 'design_' + predictionId; | ||
y = y + 2 * SPACER_PIXELS; | ||
var predictButton = designMode.createElement('BUTTON', x, y); | ||
predictButton.textContent = 'Predict'; | ||
var predictButtonId = modelData.name + '_predict'; | ||
designMode.updateProperty(predictButton, 'id', predictButtonId); | ||
var predictOnClick = `onEvent("${predictButtonId}", "click", function() { | ||
getPrediction("${ | ||
modelData.name | ||
}", "${modelId}", testValues, function(value) { | ||
setText("${predictionId}", value); | ||
}); | ||
});`; | ||
designMode.onInsertEvent(predictOnClick); | ||
} | ||
|
||
export default function autogenerateML(modelId) { | ||
return new Promise(function(resolve, reject) { | ||
$.ajax({ | ||
url: `/api/v1/ml_models/${modelId}`, | ||
method: 'GET' | ||
}) | ||
.then(modelData => { | ||
generateCodeDesignElements(modelId, modelData); | ||
return resolve(); | ||
}) | ||
.fail((jqXhr, status) => { | ||
return alert({message: 'An error occurred'}); | ||
}); | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import PropTypes from 'prop-types'; | ||
import React from 'react'; | ||
import $ from 'jquery'; | ||
import BaseDialog from '@cdo/apps/templates/BaseDialog'; | ||
import Button from '@cdo/apps/templates/Button'; | ||
|
||
export default class ModelManagerDialog extends React.Component { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this dialog will live next to and be most similar to these two components, so just linking those here in case it's helpful: The LibraryManagerDialog is newer and more consistent with our newer styles, so i'd go with that one if you're trying to decide how to style something (related to our convo here) |
||
static propTypes = { | ||
isOpen: PropTypes.bool.isRequired, | ||
onClose: PropTypes.func.isRequired, | ||
autogenerateML: PropTypes.func | ||
}; | ||
|
||
state = { | ||
models: [], | ||
isImportPending: false | ||
}; | ||
|
||
componentDidMount() { | ||
this.getModelList(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When running a regular AppLab app, we keep on hitting There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That seems to fire once, but due to the lifetime management of these dialogs (they live forever, just showing and hiding), it doesn't refresh the model list each time the dialog is opened. I have implemented what seems to be a solution in #39041. |
||
} | ||
|
||
closeModelManager = () => { | ||
this.props.onClose(); | ||
}; | ||
|
||
getModelList = () => { | ||
$.ajax({ | ||
url: '/api/v1/ml_models/names', | ||
method: 'GET' | ||
}).then(models => { | ||
this.setState({models}); | ||
}); | ||
}; | ||
|
||
importMLModel = async () => { | ||
this.setState({isImportPending: true}); | ||
const modelId = this.root.value; | ||
await this.props.autogenerateML(modelId); | ||
this.setState({isImportPending: false}); | ||
this.closeModelManager(); | ||
}; | ||
|
||
render() { | ||
const {isOpen} = this.props; | ||
const noModels = this.state.models.length === 0; | ||
|
||
return ( | ||
<div> | ||
<BaseDialog | ||
isOpen={isOpen} | ||
handleClose={this.closeModelManager} | ||
useUpdatedStyles | ||
> | ||
<h2>Machine Learning Models</h2> | ||
<select name="model" ref={element => (this.root = element)}> | ||
{this.state.models.map(model => ( | ||
<option key={model.id} value={model.id}> | ||
{model.name} | ||
</option> | ||
))} | ||
</select> | ||
{noModels && <div>You have not trained any AI models yet.</div>} | ||
<Button | ||
text={'Import'} | ||
color={Button.ButtonColor.orange} | ||
onClick={this.importMLModel} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this function does most of its work asynchronously, should we show a spinner or something similar while it does its work? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Screen.Recording.2021-02-01.at.9.29.13.PM.mov |
||
disabled={noModels} | ||
isPending={this.state.isImportPending} | ||
pendingText={'Importing...'} | ||
/> | ||
<h3>Model card details will go here.</h3> | ||
</BaseDialog> | ||
</div> | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
/* global Promise */ | ||
|
||
import $ from 'jquery'; | ||
import {predict} from '@cdo/apps/MLTrainers'; | ||
|
||
export const commands = { | ||
async getPrediction(opts) { | ||
return new Promise((resolve, reject) => { | ||
$.ajax({ | ||
url: '/api/v1/ml_models/' + opts.modelId, | ||
method: 'GET' | ||
}) | ||
.then(modelData => { | ||
const predictParams = { | ||
...modelData, | ||
testData: opts.testValues | ||
}; | ||
const result = predict(predictParams); | ||
opts.callback(result); | ||
return resolve(); | ||
}) | ||
.fail((jqXhr, status) => { | ||
opts.callback('Error: prediction failed'); | ||
return reject({message: 'An error occurred'}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, it does seem we might want to expose this error to the user's program, so they can then report it back to their end-user. |
||
}); | ||
}); | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see a lot of "ml" related naming, but it sounds like we are going to trend towards "ai" instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah - it'd be good to get all on the same page about that and update the code to match. Ok to do as a rename/refactor after this PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds fine.