diff --git a/public/images/ai-bot-closed.png b/public/images/ai-bot-closed.png
index bf64e52c..d45ee573 100644
Binary files a/public/images/ai-bot-closed.png and b/public/images/ai-bot-closed.png differ
diff --git a/public/index.html b/public/index.html
index 82dc97b8..55d349c5 100644
--- a/public/index.html
+++ b/public/index.html
@@ -4,12 +4,6 @@
ML Activities Playground
-
-
+
-
-
-
-
-
diff --git a/src/MLActivities.jsx b/src/MLActivities.jsx
deleted file mode 100644
index 5a93e00f..00000000
--- a/src/MLActivities.jsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import React from 'react';
-import RPS from './activities/rps/RPS';
-import ImageRecognition from './activities/imageRecognition/ImageRecognition';
-import Button from 'react-bootstrap/lib/Button';
-import Row from 'react-bootstrap/lib/Row';
-import Col from 'react-bootstrap/lib/Col';
-import Grid from 'react-bootstrap/lib/Grid';
-import Panel from 'react-bootstrap/lib/Panel';
-
-const Activity = Object.freeze({
- None: 0,
- RPS: 1,
- ImageRecognition: 2
-});
-
-module.exports = class MLActivities extends React.Component {
- state = {
- currentActivity: Activity.None
- };
-
- render() {
- return (
-
-
-
-
- ML Activities Playground
- {this.state.currentActivity !== Activity.None && (
-
- this.setState({
- currentActivity: Activity.None
- })
- }
- style={{marginBottom: 10}}
- >
- 👈 Pick Another Activity
-
- )}
- {this.state.currentActivity === Activity.None && (
-
- this.setState({currentActivity: Activity.RPS})}
- >
- Pick RPS Activity
-
-
- this.setState({
- currentActivity: Activity.ImageRecognition
- })
- }
- >
- Pick Image Recognition Activity
-
-
- )}
- {this.state.currentActivity === Activity.RPS && (
-
-
-
- )}
- {this.state.currentActivity === Activity.ImageRecognition && (
-
-
-
- )}
-
-
-
-
- );
- }
-};
diff --git a/src/activities/imageRecognition/Draggable.jsx b/src/activities/imageRecognition/Draggable.jsx
deleted file mode 100644
index 8bbcd8a6..00000000
--- a/src/activities/imageRecognition/Draggable.jsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import React from 'react';
-import $ from 'jquery';
-import 'jquery-ui/ui/widgets/draggable';
-import * as PropTypes from 'react/lib/ReactPropTypes';
-window.jQuery = $;
-require('jquery-ui-touch-punch');
-
-module.exports = class ImageRecognition extends React.Component {
- componentDidMount() {
- $(this.draggableDiv).draggable({revert: true});
- }
-
- render() {
- return (
- (this.draggableDiv = element)}
- >
- {this.props.children}
-
- );
- }
-};
-
-module.exports.propTypes = {
- children: PropTypes.node,
- guid: PropTypes.string
-};
diff --git a/src/activities/imageRecognition/Droppable.jsx b/src/activities/imageRecognition/Droppable.jsx
deleted file mode 100644
index b70676ef..00000000
--- a/src/activities/imageRecognition/Droppable.jsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import React from 'react';
-import $ from 'jquery';
-import 'jquery-ui/ui/effects/effect-drop';
-import 'jquery-ui/ui/widgets/droppable';
-import * as PropTypes from 'react/lib/ReactPropTypes';
-window.jQuery = $;
-require('jquery-ui-touch-punch');
-
-module.exports = class ImageRecognition extends React.Component {
- componentDidMount() {
- $(this.droppableDiv).droppable({
- classes: {
- 'ui-droppable-active': 'ui-state-active',
- 'ui-droppable-hover': 'ui-state-hover'
- },
- tolerance: 'touch',
- drop: (event, ui) => {
- this.props.onDrop(ui.draggable.data('guid'));
- }
- });
- }
-
- render() {
- return (
- (this.droppableDiv = element)}
- >
- {this.props.children}
-
- );
- }
-};
-
-module.exports.propTypes = {
- children: PropTypes.node,
- onDrop: PropTypes.func
-};
diff --git a/src/activities/imageRecognition/ImageRecognition.jsx b/src/activities/imageRecognition/ImageRecognition.jsx
deleted file mode 100644
index c3bfe012..00000000
--- a/src/activities/imageRecognition/ImageRecognition.jsx
+++ /dev/null
@@ -1,320 +0,0 @@
-import React from 'react';
-import SimpleTrainer from '../../utils/SimpleTrainer';
-import Draggable from './Draggable';
-import Droppable from './Droppable';
-import TrainingImageUpload from './TrainingImageUpload';
-import PredictionUpload from './PredictionUpload';
-import Row from 'react-bootstrap/lib/Row';
-import Col from 'react-bootstrap/lib/Col';
-import Button from 'react-bootstrap/lib/Button';
-
-const ActivityState = Object.freeze({
- Loading: 0,
- Training: 1,
- Playing: 2
-});
-
-const NO_PREDICTION = -1;
-const defaultState = {
- classes: [
- {
- name: 'Dogs',
- examples: 0
- },
- {
- name: 'Cats',
- examples: 0
- }
- ],
- activityState: ActivityState.Loading,
- predictedClass: NO_PREDICTION
-};
-
-const activityImages = [
- {guid: 'a', url: 'images/dog1.png'},
- {guid: 'b', url: 'images/dog2.png'},
- {guid: 'c', url: 'images/dog3.png'},
- {guid: 'd', url: 'images/cat1.jpg'},
- {guid: 'e', url: 'images/cat2.jpg'},
- {guid: 'f', url: 'images/cat3.jpg'}
-];
-
-const IMAGE_SIZE = 227;
-
-function loadImage(url, size) {
- return new Promise(resolve => {
- const image = new Image();
- image.width = size;
- image.height = size;
- image.addEventListener('load', () => {
- resolve(image);
- });
- image.src = url;
- });
-}
-
-module.exports = class ImageRecognition extends React.Component {
- constructor(props) {
- super(props);
- this.simpleTrainer = new SimpleTrainer();
- }
-
- state = defaultState;
-
- componentDidMount() {
- this.simpleTrainer.initializeClassifiers().then(() => {
- this.setState({activityState: ActivityState.Training});
- });
- }
-
- render() {
- if (this.state.activityState === ActivityState.Loading) {
- return Loading machine learning model data...
;
- }
-
- return (
-
- {this.state.activityState === ActivityState.Training && (
-
-
-
- {this.state.classes.map((classData, i) => {
- return (
- {
- this.simpleTrainer.addExampleImage(image, i);
- this.updateExampleCounts(i);
- }}
- />
- );
- })}
-
-
-
-
- Drag images to train your machine learning algorithm
-
-
-
-
-
-
-
-
-
- {activityImages.map((image, i) => {
- return (
-
- {
- // loadImage(image.url, IMAGE_SIZE).then((img) => {
- // this.simpleTrainer.predictFromImage(img).then((result) => {
- // console.log(result);
- // });
- // });
- // }}
- src={image.url}
- className="thumbnail"
- style={{
- display: 'inline-block'
- }}
- width={100}
- height={100}
- />
-
- );
- })}
-
-
-
- {this.state.classes.map((classData, i) => {
- return (
-
- {
- const image = activityImages.find(e => {
- return e.guid === guid;
- });
- loadImage(image.url, IMAGE_SIZE).then(image => {
- this.simpleTrainer.addExampleImage(image, i);
- this.updateExampleCounts(i);
- });
- }}
- >
- {classData.name}
-
-
- {classData.examples.toString()}
-
-
- );
- })}
-
-
- )}
- {this.state.activityState === ActivityState.Training && (
-
-
- {
- this.setState({activityState: ActivityState.Playing});
- }}
- >
- Try the Model!
-
- {
- this.simpleTrainer.clearAll();
- this.resetAllExampleCounts();
- }}
- >
- Reset Training
-
-
-
- )}
- {this.state.activityState === ActivityState.Playing && (
-
-
-
- {
- this.simpleTrainer.predictFromImage(img).then(trainingResult => {
- this.setState({trainingResult});
- });
- }}
- />
-
-
-
-
- Tap an image to classify it
- {activityImages.map((image, i) => {
- return (
- {
- loadImage(image.url, IMAGE_SIZE).then(img => {
- this.simpleTrainer.predictFromImage(img).then(result => {
- this.setState({
- trainingResult: result
- });
- });
- });
- }}
- width={100}
- height={100}
- />
- );
- })}
-
-
-
- )}
- {this.state.activityState === ActivityState.Playing &&
- !!this.state.trainingResult && (
-
-
- Predicted Category:
-
- {
- this.state.classes[
- this.state.trainingResult.predictedClassId
- ].name
- }
-
-
- {JSON.stringify(this.state.trainingResult)}
-
-
- )}
- {this.state.activityState === ActivityState.Playing && (
-
-
- {
- this.setState({activityState: ActivityState.Training});
- }}
- >
- Train More
-
-
-
- )}
-
- );
- }
-
- resetAllExampleCounts() {
- const classes = this.state.classes;
- this.state.classes.forEach((c, i) => {
- classes[i].examples = 0;
- });
- this.setState({classes: classes});
- }
-
- async updateExampleCounts(i) {
- const classes = this.state.classes;
- classes[i].examples = this.simpleTrainer.getExampleCount(i);
- return this.setState({classes: classes});
- }
-
- async playRound() {
- if (this.video.isPlaying()) {
- if (this.simpleTrainer.getNumClasses() > 0) {
- let frameDataURI = this.video.getFrameDataURI(400);
-
- let predictionResult = await this.simpleTrainer.predictFromImage(
- this.video.getVideoElement()
- );
- this.setState(
- {
- lastPrediction: {
- predictedClass: predictionResult.predictedClassId,
- confidence:
- predictionResult.confidencesByClassId[
- predictionResult.predictedClassId
- ],
- playerPlayedImage: frameDataURI,
- confidencesByClassId: predictionResult.confidencesByClassId
- }
- },
- null
- );
- }
- }
- }
-};
diff --git a/src/activities/imageRecognition/PredictionUpload.jsx b/src/activities/imageRecognition/PredictionUpload.jsx
deleted file mode 100644
index d9a7e7af..00000000
--- a/src/activities/imageRecognition/PredictionUpload.jsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import React, {PropTypes} from 'react';
-
-module.exports = class SingleUpload extends React.Component {
- static propTypes = {
- predictClass: PropTypes.func
- };
-
- onUpload() {
- var file = document.getElementById('predictfile').files[0];
- if (!file) {
- return;
- }
- var url = URL.createObjectURL(file), // create an Object URL
- img = new Image(); // create a temp. image object
-
- var _this = this;
- img.onload = function() {
- _this.props.predictClass(img);
- };
-
- img.src = url; // start convertion file
- }
-
- render() {
- return (
-
- Upload your own:
- this.onUpload()} />
-
- );
- }
-};
diff --git a/src/activities/imageRecognition/TrainingImageUpload.jsx b/src/activities/imageRecognition/TrainingImageUpload.jsx
deleted file mode 100644
index 207d33ee..00000000
--- a/src/activities/imageRecognition/TrainingImageUpload.jsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import React, {PropTypes} from 'react';
-
-module.exports = class MultiUpload extends React.Component {
- static propTypes = {
- className: PropTypes.string,
- addTrainingExample: PropTypes.func
- };
-
- onUpload() {
- var files = document.getElementById(this.props.className + 'file').files;
- for (var i = 0; i < files.length; ++i) {
- var file = files[i];
- if (!file) {
- return;
- }
- var url = URL.createObjectURL(file), // create an Object URL
- img = new Image(); // create a temp. image object
- var _this = this;
- img.onload = function() {
- // The height and width doesn't always load so set them if they're 0
- if (!img.width) {
- img.width = 500;
- }
- if (!img.height) {
- img.height = 500;
- }
- _this.props.addTrainingExample(img);
- URL.revokeObjectURL(url);
- };
-
- img.src = url; // start convertion file
- }
- }
-
- render() {
- return (
-
- {'Upload your own ' + this.props.className + ':'}
- this.onUpload()}
- multiple
- />
-
- );
- }
-};
diff --git a/src/activities/rps/IntroScreen.jsx b/src/activities/rps/IntroScreen.jsx
deleted file mode 100644
index 0575ebaf..00000000
--- a/src/activities/rps/IntroScreen.jsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import React from 'react';
-import Col from 'react-bootstrap/lib/Col';
-import Row from 'react-bootstrap/lib/Row';
-import Button from 'react-bootstrap/lib/Button';
-import * as PropTypes from 'react/lib/ReactPropTypes';
-
-module.exports = class IntroScreen extends React.Component {
- constructor(props) {
- super(props);
- }
-
- render() {
- return (
-
-
- Train a computer to “see” Rock, Paper, Scissors
-
- On the next screen, you’ll be prompted to let your browser to access
- the camera. Please click “Allow”
-
- {
- this.props.onClickContinue();
- }}
- >
- Continue
-
-
- If your device doesn’t have a camera:
-
-
- Visit code.org/ai from using a modern smartphone that has a camera.
-
-
- What is the camera access for?:
-
- You’ll be “training” a computer vision algorithm.
-
- None of the images seen by the camera will ever leave your computer
- or be shared or stored by anybody. They’ll be used only for this
- tutorial and deleted after you leave this web page.
-
-
-
- );
- }
-};
-
-module.exports.propTypes = {
- onClickContinue: PropTypes.func
-};
diff --git a/src/activities/rps/PlayRound.jsx b/src/activities/rps/PlayRound.jsx
deleted file mode 100644
index 1ffde6b0..00000000
--- a/src/activities/rps/PlayRound.jsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import React from 'react';
-import Col from 'react-bootstrap/lib/Col';
-import Row from 'react-bootstrap/lib/Row';
-import * as PropTypes from 'react/lib/ReactPropTypes';
-
-module.exports = class PlayRound extends React.Component {
- constructor(props) {
- super(props);
- }
-
- state = {
- countdownNumber: 3
- };
-
- componentDidMount() {
- setTimeout(() => this.decrementCountdown(), 1000);
- setTimeout(() => this.decrementCountdown(), 2000);
- setTimeout(() => this.decrementCountdown(), 3000);
- setTimeout(() => this.props.onPlayRound(), 3500);
- }
-
- componentWillUnmount() {
- console.log('unounting');
- this.hasMounted = false;
- }
-
- decrementCountdown() {
- this.setState({countdownNumber: this.state.countdownNumber - 1});
- }
-
- render() {
- return (
-
-
- {
- if (this.hasMounted) {
- return;
- }
- this.props.onMountVideo(el);
- this.hasMounted = true;
- }}
- autoPlay=""
- playsInline=""
- width={this.props.imageSize}
- height={this.props.imageSize}
- />
-
-
-
- {this.state.countdownNumber <= 3 && `3...`}{' '}
- {this.state.countdownNumber <= 2 && `2...`}{' '}
- {this.state.countdownNumber <= 1 && `1...`}
-
-
-
- );
- }
-};
-
-module.exports.propTypes = {
- onPlayRound: PropTypes.func,
- onMountVideo: PropTypes.func,
- imageSize: PropTypes.number
-};
diff --git a/src/activities/rps/PlayRoundInstructions.jsx b/src/activities/rps/PlayRoundInstructions.jsx
deleted file mode 100644
index 4a3e86ce..00000000
--- a/src/activities/rps/PlayRoundInstructions.jsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import React from 'react';
-import Col from 'react-bootstrap/lib/Col';
-import Row from 'react-bootstrap/lib/Row';
-import Button from 'react-bootstrap/lib/Button';
-import * as PropTypes from 'react/lib/ReactPropTypes';
-
-module.exports = class PlayRoundInstructions extends React.Component {
- constructor(props) {
- super(props);
- }
-
- render() {
- return (
-
-
-
-
- Have you provided enough training data?
-
-
- Did you take enough photos for the machine learning algorithm to
- distinguish ROCK from PAPER from SCISSORS? Let’s find out!
-
-
-
-
-
- {
- this.props.onClickContinue();
- }}
- >
- Try the Game!
-
-
-
-
-
-
- If the computer vision doesn’t seem to work very well, try going
- back to Train more.
-
-
- If it works well, see if it can recognize somebody else’s hand,
- especially somebody with different skin color.
-
-
-
-
- );
- }
-};
-
-module.exports.propTypes = {
- onClickContinue: PropTypes.func
-};
diff --git a/src/activities/rps/PlayRoundResult.jsx b/src/activities/rps/PlayRoundResult.jsx
deleted file mode 100644
index 857b4d36..00000000
--- a/src/activities/rps/PlayRoundResult.jsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import React from 'react';
-import Col from 'react-bootstrap/lib/Col';
-import Button from 'react-bootstrap/lib/Button';
-import Row from 'react-bootstrap/lib/Row';
-import * as PropTypes from 'react/lib/ReactPropTypes';
-
-module.exports = class PlayRound extends React.Component {
- constructor(props) {
- super(props);
- }
-
- componentDidMount() {}
-
- render() {
- return (
-
-
-
-
- {this.props.winner > 0
- ? 'YOU WIN! 🙌'
- : this.props.winner === 0
- ? 'DRAW 🤷♀️'
- : 'YOU LOSE 😭'}
-
-
-
-
-
-
- You:
-
-
-
- {this.props.playerPlayed} (
- {Math.floor(this.props.confidence * 100)}% sure)
-
-
-
-
- Computer:
-
-
- {this.props.computerPlayedEmoji}
-
- {this.props.computerPlayed}
-
-
-
-
-
- Play Again
-
-
-
-
- Train More
-
-
-
-
-
- Continue
-
-
-
- );
- }
-};
-
-module.exports.propTypes = {
- onPlayAgain: PropTypes.func,
- onTrainMore: PropTypes.func,
- onContinue: PropTypes.func,
- roundResult: PropTypes.object,
-
- confidence: PropTypes.number,
-
- playerPlayed: PropTypes.string,
- playerPlayedImage: PropTypes.string,
- computerPlayed: PropTypes.string,
- computerPlayedEmoji: PropTypes.string,
-
- winner: PropTypes.number
-};
diff --git a/src/activities/rps/RPS.jsx b/src/activities/rps/RPS.jsx
deleted file mode 100644
index 32707777..00000000
--- a/src/activities/rps/RPS.jsx
+++ /dev/null
@@ -1,246 +0,0 @@
-/**
- * RPS Activity. Based off of:
- * - https://github.com/ryansloan/rps-ml
- * - https://github.com/googlecreativelab/teachable-machine-boilerplate
- */
-
-import React from 'react';
-
-import SimpleTrainer from '../../utils/SimpleTrainer';
-import Video from '../../utils/Video.js';
-
-import IntroScreen from './IntroScreen';
-import TrainingScreen from './TrainingScreen';
-import PlayRound from './PlayRound';
-import PlayRoundInstructions from './PlayRoundInstructions';
-import PlayRoundResult from './PlayRoundResult';
-
-const IMAGE_SIZE = 227;
-const CLASS_NAMES = ['rock', 'paper', 'scissors'];
-
-const NO_CLASS = -1;
-
-const ActivityScreen = Object.freeze({
- IntroInstructions: 0,
- TrainClass1: 1,
- TrainClass2: 2,
- TrainClass3: 3,
- PlayRoundInstructions: 4,
- PlayRound: 5,
- PlayRoundResult: 6
-});
-
-const defaultState = {
- currentScreen: ActivityScreen.IntroInstructions,
- predictedClass: NO_CLASS,
- confidencesByClassId: [],
- trainingImages0: [],
- trainingImages1: [],
- trainingImages2: [],
- roundResult: null,
- roundPrediction: null
-};
-module.exports = class Main extends React.Component {
- constructor(props) {
- super(props);
- this.video = new Video(IMAGE_SIZE);
- this.simpleTrainer = new SimpleTrainer();
- }
-
- state = defaultState;
-
- componentDidMount() {
- this.simpleTrainer.initializeClassifiers();
- }
-
- rpsToEmoji(rps) {
- switch (rps) {
- case 'rock':
- return '✊';
- case 'scissors':
- return '✌';
- default:
- return '✋';
- }
- }
-
- render() {
- return (
-
- {this.state.currentScreen === ActivityScreen.IntroInstructions && (
-
{
- this.setState(
- {
- currentScreen: ActivityScreen.TrainClass1
- },
- null
- );
- }}
- />
- )}
- {this.trainingScreen(0)}
- {this.trainingScreen(1)}
- {this.trainingScreen(2)}
- {this.state.currentScreen === ActivityScreen.PlayRoundInstructions && (
- {
- this.setState(
- {
- currentScreen: ActivityScreen.PlayRound
- },
- null
- );
- }}
- />
- )}
- {this.state.currentScreen === ActivityScreen.PlayRound && (
- {
- this.video.loadVideo(videoElement);
- }}
- onPlayRound={() => {
- this.playRound().then(() => {
- return this.setState(
- {
- currentScreen: ActivityScreen.PlayRoundResult
- },
- null
- );
- });
- }}
- />
- )}
- {this.state.currentScreen === ActivityScreen.PlayRoundResult && (
- {
- this.setState(
- {
- currentScreen: ActivityScreen.PlayRound
- },
- null
- );
- }}
- onTrainMore={() => {
- this.setState(
- {
- currentScreen: ActivityScreen.TrainClass1
- },
- null
- );
- }}
- onContinue={() => {
- this.setState(defaultState, null);
- }}
- />
- )}
-
- );
- }
-
- trainingScreen(index) {
- const thisScreen = ActivityScreen[`TrainClass${index + 1}`];
- const nextScreen =
- index + 1 >= CLASS_NAMES.length
- ? ActivityScreen.PlayRoundInstructions
- : ActivityScreen[`TrainClass${index + 2}`];
- return (
- this.state.currentScreen === thisScreen && (
- {
- this.trainExample(index);
- }}
- onContinueClicked={() => {
- this.setState(
- {
- currentScreen: nextScreen
- },
- null
- );
- }}
- onMountVideo={videoElement => {
- this.video.loadVideo(videoElement);
- }}
- imageSize={IMAGE_SIZE}
- trainingClass={CLASS_NAMES[index]}
- exampleCount={this.simpleTrainer.getExampleCount(index)}
- trainingImages={this.state[`trainingImages${index}`]}
- />
- )
- );
- }
-
- /**
- * @param {number} index
- * @returns {Promise}
- */
- async trainExample(index) {
- if (this.video.isPlaying()) {
- this.simpleTrainer.addExampleImage(this.video.getVideoElement(), index);
- this.setState({
- ['trainingImages' + index]: this.state['trainingImages' + index].concat(
- this.video.getFrameDataURI()
- )
- });
- }
- }
-
- async playRound() {
- if (this.video.isPlaying()) {
- if (this.simpleTrainer.getNumClasses() > 0) {
- let frameDataURI = this.video.getFrameDataURI(400);
-
- let predictionResult = await this.simpleTrainer.predictFromImage(
- this.video.getVideoElement()
- );
- let computerChoice = CLASS_NAMES[Math.floor(Math.random() * 3)];
- let playerChoice = CLASS_NAMES[predictionResult.predictedClassId];
- const winner = this.pickWinner(playerChoice, computerChoice);
-
- this.setState(
- {
- roundPrediction: {
- predictedClass: predictionResult.predictedClassId,
- confidence:
- predictionResult.confidencesByClassId[
- predictionResult.predictedClassId
- ],
- playerPlayedImage: frameDataURI,
-
- playerPlayed: playerChoice,
- computerPlayed: computerChoice,
- computerPlayedEmoji: this.rpsToEmoji(computerChoice),
-
- confidencesByClassId: predictionResult.confidencesByClassId
- },
- roundResult: {
- winner: winner
- }
- },
- null
- );
- }
- }
- }
-
- pickWinner(playerChoice, computerChoice) {
- return this.beats(playerChoice, computerChoice);
- }
-
- beats(x, y) {
- const keyBeatsValue = {
- rock: 'scissors',
- paper: 'rock',
- scissors: 'paper'
- };
-
- return keyBeatsValue[x] === y ? 1 : keyBeatsValue[y] === x ? -1 : 0;
- }
-};
diff --git a/src/activities/rps/TrainingScreen.jsx b/src/activities/rps/TrainingScreen.jsx
deleted file mode 100644
index cf0cf9c7..00000000
--- a/src/activities/rps/TrainingScreen.jsx
+++ /dev/null
@@ -1,105 +0,0 @@
-import React from 'react';
-import Col from 'react-bootstrap/lib/Col';
-import Button from 'react-bootstrap/lib/Button';
-import Row from 'react-bootstrap/lib/Row';
-import * as PropTypes from 'react/lib/ReactPropTypes';
-
-module.exports = class TrainingScreen extends React.Component {
- constructor(props) {
- super(props);
- }
-
- componentWillUnmount() {
- this.hasMounted = false;
- }
-
- render() {
- return (
-
-
-
-
-
- “Train” the computer to see{' '}
- {this.props.trainingClass.endsWith('s') ? '' : 'a'}{' '}
- {this.props.trainingClass}
-
-
-
- Show a rock, and click Train to take photos, so the machine
- learning algorithm can “learn” how to recognize a rock.
-
-
-
-
-
- {
- if (this.hasMounted) {
- return;
- }
- this.props.onMountVideo(el);
- this.hasMounted = true;
- }}
- autoPlay=""
- playsInline=""
- width={this.props.imageSize}
- height={this.props.imageSize}
- />
-
-
-
-
- {
- this.props.onTrainClicked();
- }}
- >
- Train {this.props.trainingClass}
-
-
-
-
-
- {this.props.trainingImages.map((image, i) => {
- return (
-
- );
- })}
-
-
- {this.props.trainingImages && this.props.trainingImages.length > 0 && (
-
-
- {
- this.props.onContinueClicked();
- }}
- >
- Continue
-
-
-
- )}
-
- );
- }
-};
-
-module.exports.propTypes = {
- onMountVideo: PropTypes.func,
- onTrainClicked: PropTypes.func,
- onContinueClicked: PropTypes.func,
- imageSize: PropTypes.number,
- trainingClass: PropTypes.string,
-
- exampleCount: PropTypes.number,
- trainingImages: PropTypes.arrayOf(PropTypes.string)
-};
diff --git a/src/demo/helpers.js b/src/demo/helpers.js
index bd6a77cd..c9b406b4 100644
--- a/src/demo/helpers.js
+++ b/src/demo/helpers.js
@@ -9,6 +9,9 @@ export const backgroundPathForMode = mode => {
imgName = 'underwater';
}
+ // Temporarily show background for every mode.
+ imgName = 'underwater';
+
return imgName ? backgroundPath(imgName) : null;
};
diff --git a/src/demo/index.jsx b/src/demo/index.jsx
index bc163147..3901928e 100644
--- a/src/demo/index.jsx
+++ b/src/demo/index.jsx
@@ -1,28 +1,51 @@
import $ from 'jquery';
import constants, {Modes} from './constants';
-import {setState} from './state';
+import {setState, setSetStateCallback} from './state';
import {init as initScene} from './init';
import {render} from './renderer';
+import 'babel-polyfill';
+import ReactDOM from 'react-dom';
+import React from 'react';
+import UI from './ui';
+
$(document).ready(() => {
- // Set up initial state
+ // Set up canvases.
const canvas = document.getElementById('activity-canvas');
const backgroundCanvas = document.getElementById('background-canvas');
canvas.width = backgroundCanvas.width = constants.canvasWidth;
canvas.height = backgroundCanvas.height = constants.canvasHeight;
+ // Temporarily use URL parameter to set some state.
+ const smallWordSet = window.location.href.indexOf("words=small") !== -1;
+
+ // Set initial state for UI elements.
setState({
currentMode: Modes.Loading,
canvas,
backgroundCanvas,
uiContainer: document.getElementById('ui-container'),
headerContainer: document.getElementById('header-container'),
- footerContainer: document.getElementById('footer-container')
+ footerContainer: document.getElementById('footer-container'),
+ smallWordSet: smallWordSet
});
+ // Initialize our first model.
initScene();
- // Start the renderer. It will self-perpetute by calling
+ // Start the canvas renderer. It will self-perpetute by calling
// requestAnimationFrame on itself.
render();
+
+ // Render the UI.
+ renderUI();
+
+ // And have the render UI handler be called every time state is set.
+ setSetStateCallback(renderUI);
});
+
+// Tell React to explicitly render the UI.
+export const renderUI = () => {
+ const renderElement = document.getElementById('container-react');
+ ReactDOM.render( , renderElement);
+};
diff --git a/src/demo/models/train.js b/src/demo/models/train.js
index c0547d3f..352106ca 100644
--- a/src/demo/models/train.js
+++ b/src/demo/models/train.js
@@ -71,7 +71,7 @@ const uiElements = state => {
];
};
-const onClassifyFish = doesLike => {
+export const onClassifyFish = doesLike => {
const state = getState();
// No-op if animation is currently in progress.
diff --git a/src/demo/renderer.js b/src/demo/renderer.js
index c157e5c7..1bef3ffc 100644
--- a/src/demo/renderer.js
+++ b/src/demo/renderer.js
@@ -166,7 +166,7 @@ const getOffsetForTime = (t, totalFish) => {
let amount = t / moveTime;
// Apply an S-curve to that amount.
- amount = amount - Math.sin(amount*2*Math.PI) / (2*Math.PI);
+ amount = amount - Math.sin(amount * 2 * Math.PI) / (2 * Math.PI);
return (
constants.fishCanvasWidth * totalFish -
@@ -195,7 +195,7 @@ const getYForFish = (numFish, fishIdx, state, offsetX, predictedClassId) => {
// Move fish down a little on predict screen.
if (state.currentMode === Modes.Predicting) {
- y += 100;
+ y += 130;
// And drop the fish down even more if they are not liked.
const doesLike = predictedClassId === ClassType.Like;
@@ -207,6 +207,12 @@ const getYForFish = (numFish, fishIdx, state, offsetX, predictedClassId) => {
y += screenX - midScreenX;
}
}
+
+ // And sway fish vertically on the predicting screen.
+ const swayValue =
+ (($time() * 360) / (20 * 1000) + (fishIdx + 1) * 10) % 360;
+ const swayOffsetY = Math.sin(((swayValue * Math.PI) / 180) * 6) * 8;
+ y += swayOffsetY;
}
return y;
@@ -248,7 +254,11 @@ const drawMovingFish = state => {
if (state.currentMode === Modes.Predicting) {
if (fish.result) {
- drawPrediction(fish.result.predictedClassId, state.word, x, y, ctx);
+ const midScreenX =
+ constants.canvasWidth / 2 - constants.fishCanvasWidth / 2;
+ if (x > midScreenX) {
+ drawPrediction(fish.result.predictedClassId, state.word, x, y, ctx);
+ }
} else {
predictFish(state, i).then(prediction => {
fish.result = prediction;
@@ -289,7 +299,7 @@ const drawFrame = state => {
size,
size,
'#F0F0F0',
- '#000000'
+ '#F0F0F0'
);
};
@@ -339,9 +349,10 @@ const drawPondFishImages = () => {
const canvas = getState().canvas;
const ctx = canvas.getContext('2d');
getState().pondFish.forEach(fish => {
- var swayValue = (($time() * 360) / (20 * 1000) + (fish.id + 1) * 10) % 360;
- var swayOffsetX = Math.sin(((swayValue * Math.PI) / 180) * 2) * 120;
- var swayOffsetY = Math.sin(((swayValue * Math.PI) / 180) * 6) * 8;
+ const swayValue =
+ (($time() * 360) / (20 * 1000) + (fish.id + 1) * 10) % 360;
+ const swayOffsetX = Math.sin(((swayValue * Math.PI) / 180) * 2) * 120;
+ const swayOffsetY = Math.sin(((swayValue * Math.PI) / 180) * 6) * 8;
drawSingleFish(fish, fish.x + swayOffsetX, fish.y + swayOffsetY, ctx);
});
@@ -445,8 +456,8 @@ export const clearCanvas = canvas => {
// Draw an overlay over the whole scene. Used for fades.
function drawOverlays() {
- var duration = $time() - currentModeStartTime;
- var amount = 1 - duration / 800;
+ const duration = $time() - currentModeStartTime;
+ let amount = 1 - duration / 800;
if (amount < 0) {
amount = 0;
}
diff --git a/src/demo/state.js b/src/demo/state.js
index 708edf9f..20617f00 100644
--- a/src/demo/state.js
+++ b/src/demo/state.js
@@ -1,3 +1,5 @@
+let setStateCallback = null;
+
const initialState = {
currentMode: null,
fishData: [],
@@ -26,5 +28,14 @@ export const getState = function() {
export const setState = function(newState) {
state = {...state, ...newState};
+
+ if (setStateCallback) {
+ setStateCallback();
+ }
+
return state;
};
+
+export const setSetStateCallback = (callback) => {
+ setStateCallback = callback;
+}
diff --git a/src/demo/ui.jsx b/src/demo/ui.jsx
new file mode 100644
index 00000000..a1f91a44
--- /dev/null
+++ b/src/demo/ui.jsx
@@ -0,0 +1,416 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {getState, setState} from './state';
+import {Modes} from './constants';
+import {toMode} from './helpers';
+import {init as initScene} from './init';
+import {onClassifyFish} from './models/train';
+
+const styles = {
+ header: {
+ position: 'absolute',
+ top: 10,
+ width: '100%',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ fontSize: 48
+ },
+ footer: {
+ position: 'absolute',
+ bottom: 0,
+ width: '100%',
+ display: 'flex',
+ justifyContent: 'space-between'
+ },
+ body: {
+ position: 'relative',
+ width: '100%',
+ paddingTop: '56.25%' // for 16:9
+ },
+ content: {
+ position: 'absolute',
+ top: '10%',
+ left: 0,
+ width: '100%'
+ },
+ button: {
+ cursor: 'pointer'
+ },
+ continueButton: {
+ marginLeft: 'auto',
+ marginRight: 10,
+ marginBottom: 10
+ },
+ button1col: {
+ width: '20%',
+ display: 'block',
+ margin: '0 auto',
+ marginTop: '2%',
+ marginBottom: '2%'
+ },
+ button3col: {
+ width: '20%',
+ marginLeft: '6%',
+ marginRight: '6%',
+ marginTop: '2%',
+ marginBottom: '2%'
+ },
+ activityIntroText: {
+ position: 'absolute',
+ fontSize: 22,
+ top: '20%',
+ left: '50%',
+ width: '80%',
+ transform: 'translateX(-50%)',
+ textAlign: 'center'
+ },
+ trainingIntroBot: {
+ position: 'absolute',
+ transform: 'translateX(-50%)',
+ top: '30%',
+ left: '50%'
+ },
+ activityIntroBot: {
+ position: 'absolute',
+ transform: 'translateX(-50%)',
+ top: '50%',
+ left: '50%'
+ },
+ wordsText: {
+ textAlign: 'center',
+ marginTop: 20,
+ fontSize: 22
+ },
+ trainQuestionText: {
+ position: 'absolute',
+ top: '18%',
+ left: '50%',
+ transform: 'translateX(-50%)',
+ fontSize: 22
+ },
+ trainQuestionTextDisabled: {
+ position: 'absolute',
+ top: '18%',
+ left: '50%',
+ transform: 'translateX(-50%)',
+ fontSize: 22,
+ opacity: 0.5
+ },
+ trainButtonYes: {
+ position: 'absolute',
+ top: '80%',
+ left: '30%'
+ },
+ trainButtonNo: {
+ position: 'absolute',
+ top: '80%',
+ left: '60%'
+ },
+ trainBot: {
+ position: 'absolute',
+ height: '50%',
+ top: '20%',
+ left: '70%'
+ },
+ predictBot: {
+ position: 'absolute',
+ height: '50%',
+ top: '2%',
+ left: '50%',
+ transform: 'translateX(-50%)'
+ },
+ pondText: {
+ position: 'absolute',
+ bottom: '3%',
+ left: '55%',
+ transform: 'translateX(-45%)',
+ fontSize: 22,
+ width: '70%',
+ backgroundColor: 'rgba(0,0,0,0.5)',
+ padding: '2%',
+ borderRadius: 10,
+ color: 'white',
+ lineHeight: '32px'
+ },
+ pondBot: {
+ position: 'absolute',
+ height: '50%',
+ left: 0,
+ bottom: 0
+ }
+};
+
+class Body extends React.Component {
+ static propTypes = {
+ children: PropTypes.node
+ };
+
+ render() {
+ return {this.props.children}
;
+ }
+}
+
+class Header extends React.Component {
+ static propTypes = {
+ children: PropTypes.node
+ };
+
+ render() {
+ return {this.props.children}
;
+ }
+}
+
+class Content extends React.Component {
+ static propTypes = {
+ children: PropTypes.node
+ };
+
+ render() {
+ return {this.props.children}
;
+ }
+}
+
+class Footer extends React.Component {
+ static propTypes = {
+ children: PropTypes.node
+ };
+
+ render() {
+ return {this.props.children}
;
+ }
+}
+
+class Button extends React.Component {
+ static propTypes = {
+ style: PropTypes.object,
+ children: PropTypes.node,
+ onClick: PropTypes.func
+ };
+
+ render() {
+ return (
+
+ {this.props.children}
+
+ );
+ }
+}
+
+class ActivityIntro extends React.Component {
+ render() {
+ return (
+
+
+
+
+ Machine learning and Artificial Intelligence (AI) can give
+ recommendations, like when a computer suggests videos to watch or
+ products to buy. What else can we teach a computer?
+
+
+ Next, you’re going to teach A.I. a new word just by showing examples
+ of that type of fish.
+
+
+
+ toMode(Modes.Words)}
+ >
+ Continue
+
+
+
+
+ );
+ }
+}
+
+class Words extends React.Component {
+ items = [
+ ['Blue', 'Green', 'Red', 'Round', 'Square'],
+ [
+ 'Friendly',
+ 'Funny',
+ 'Bizarre',
+ 'Shy',
+ 'Glitchy',
+ 'Delicious',
+ 'Fun',
+ 'Angry',
+ 'Fast',
+ 'Smart',
+ 'Brave',
+ 'Scary',
+ 'Wild',
+ 'Fierce',
+ 'Tropical'
+ ]
+ ];
+
+ currentItems() {
+ const state = getState();
+ const itemSet = state.smallWordSet ? 0 : 1;
+
+ return this.items[itemSet];
+ }
+
+ onChangeWord(itemIndex) {
+ setState({
+ word: this.currentItems()[itemIndex],
+ currentMode: Modes.TrainingIntro
+ });
+ initScene();
+ }
+
+ render() {
+ const state = getState();
+ const currentItems = this.currentItems();
+ const buttonStyle = state.smallWordSet
+ ? styles.button1col
+ : styles.button3col;
+
+ return (
+
+
+
+
+ What type of fish do you want to train A.I. to detect?
+
+ {currentItems.map((item, itemIndex) => (
+ this.onChangeWord(itemIndex)}
+ >
+ {item}
+
+ ))}
+
+
+ );
+ }
+}
+
+class TrainingIntro extends React.Component {
+ render() {
+ const state = getState();
+
+ return (
+
+
+
+ Now let's teach A.I. what {state.word.toUpperCase()} fish look
+ like.
+
+
+
+ toMode(Modes.Training)}
+ >
+ Continue
+
+
+
+ );
+ }
+}
+
+class Train extends React.Component {
+ render() {
+ const state = getState();
+ const questionText = `Is this fish ${state.word.toUpperCase()}?`;
+ const trainQuestionTextStyle = state.isRunning
+ ? styles.trainQuestionTextDisabled
+ : styles.trainQuestionText;
+
+ return (
+
+
+ {questionText}
+
+ onClassifyFish(true)}
+ >
+ Yes
+
+ onClassifyFish(false)}
+ >
+ No
+
+
+ toMode(Modes.Predicting)}
+ >
+ Continue
+
+
+
+ );
+ }
+}
+
+class Predict extends React.Component {
+ render() {
+ return (
+
+
+
+
+ toMode(Modes.Pond)}
+ >
+ Skip
+
+
+
+ );
+ }
+}
+
+class Pond extends React.Component {
+ render() {
+ const state = getState();
+ const pondText = `Out of ${
+ state.fishData.length
+ } objects, A.I. identified ${
+ state.pondFish.length
+ } that it classified as ${state.word.toUpperCase()}.`;
+
+ return (
+
+
+ {pondText}
+
+
+ );
+ }
+}
+
+module.exports = class UI extends React.Component {
+ render() {
+ const mode = getState().currentMode;
+
+ return (
+
+ {mode === Modes.ActivityIntro &&
}
+ {mode === Modes.Words &&
}
+ {mode === Modes.TrainingIntro &&
}
+ {mode === Modes.Training &&
}
+ {mode === Modes.Predicting &&
}
+ {mode === Modes.Pond &&
}
+
+ );
+ }
+};
diff --git a/src/index.js b/src/index.js
deleted file mode 100644
index 98b566e2..00000000
--- a/src/index.js
+++ /dev/null
@@ -1 +0,0 @@
-alert('Hello world! -index.jsx');
diff --git a/webpack.config.js b/webpack.config.js
index 29e95e54..3bd469a2 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -4,7 +4,6 @@ module.exports = {
extensions: ['.js', '.jsx'],
},
entry: {
- main: './src/index.js',
demo: './src/demo/index.jsx',
},
output: {