173 |
174 |
175 |
177 |
178 |
179 | License
180 |
181 | MIT License
182 |
183 | Copyright (c) 2022 OSLabs Beta
184 |
185 | Permission is hereby granted, free of charge, to any person obtaining a copy
186 | of this software and associated documentation files (the "Software"), to deal
187 | in the Software without restriction, including without limitation the rights
188 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
189 | copies of the Software, and to permit persons to whom the Software is
190 | furnished to do so, subject to the following conditions:
191 |
192 | The above copyright notice and this permission notice shall be included in all
193 | copies or substantial portions of the Software.
194 |
195 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
196 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
197 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
198 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
199 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
200 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
201 | SOFTWARE.
202 |
203 |
204 |
205 |
206 | The Team
207 |
208 | - [Mark Alexander](https://github.com/MarkA772)
209 | - [Saif Beiruty](https://github.com/saifbeiruty)
210 | - [Laurence Diarra](https://github.com/ld17282)
211 | - [Mike Oakes](https://github.com/MOakes7)
212 | - [Sabre Nguyen](https://github.com/klsabren)
213 |
214 |
215 |
--------------------------------------------------------------------------------
/client/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/.DS_Store
--------------------------------------------------------------------------------
/client/assets/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/.DS_Store
--------------------------------------------------------------------------------
/client/assets/Aeonik/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/.DS_Store
--------------------------------------------------------------------------------
/client/assets/Aeonik/Aeonik-Air.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-Air.otf
--------------------------------------------------------------------------------
/client/assets/Aeonik/Aeonik-AirItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-AirItalic.otf
--------------------------------------------------------------------------------
/client/assets/Aeonik/Aeonik-Black.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-Black.otf
--------------------------------------------------------------------------------
/client/assets/Aeonik/Aeonik-BlackItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-BlackItalic.otf
--------------------------------------------------------------------------------
/client/assets/Aeonik/Aeonik-Bold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-Bold.otf
--------------------------------------------------------------------------------
/client/assets/Aeonik/Aeonik-BoldItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-BoldItalic.otf
--------------------------------------------------------------------------------
/client/assets/Aeonik/Aeonik-Light.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-Light.otf
--------------------------------------------------------------------------------
/client/assets/Aeonik/Aeonik-LightItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-LightItalic.otf
--------------------------------------------------------------------------------
/client/assets/Aeonik/Aeonik-Medium.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-Medium.otf
--------------------------------------------------------------------------------
/client/assets/Aeonik/Aeonik-MediumItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-MediumItalic.otf
--------------------------------------------------------------------------------
/client/assets/Aeonik/Aeonik-Regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-Regular.otf
--------------------------------------------------------------------------------
/client/assets/Aeonik/Aeonik-RegularItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-RegularItalic.otf
--------------------------------------------------------------------------------
/client/assets/Aeonik/Aeonik-Thin.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-Thin.otf
--------------------------------------------------------------------------------
/client/assets/Aeonik/Aeonik-ThinItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Aeonik/Aeonik-ThinItalic.otf
--------------------------------------------------------------------------------
/client/assets/Group 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Group 1.png
--------------------------------------------------------------------------------
/client/assets/Group 2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Group 2.png
--------------------------------------------------------------------------------
/client/assets/Group 3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Group 3.png
--------------------------------------------------------------------------------
/client/assets/Group 4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Group 4.png
--------------------------------------------------------------------------------
/client/assets/Group 5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Group 5.png
--------------------------------------------------------------------------------
/client/assets/Group 6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Group 6.png
--------------------------------------------------------------------------------
/client/assets/Hando Soft/HandoSoft-Black.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-Black.ttf
--------------------------------------------------------------------------------
/client/assets/Hando Soft/HandoSoft-BlackOblique.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-BlackOblique.ttf
--------------------------------------------------------------------------------
/client/assets/Hando Soft/HandoSoft-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-Bold.ttf
--------------------------------------------------------------------------------
/client/assets/Hando Soft/HandoSoft-BoldOblique.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-BoldOblique.ttf
--------------------------------------------------------------------------------
/client/assets/Hando Soft/HandoSoft-ExtraBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-ExtraBold.ttf
--------------------------------------------------------------------------------
/client/assets/Hando Soft/HandoSoft-ExtraBoldOblique.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-ExtraBoldOblique.ttf
--------------------------------------------------------------------------------
/client/assets/Hando Soft/HandoSoft-ExtraLight.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-ExtraLight.ttf
--------------------------------------------------------------------------------
/client/assets/Hando Soft/HandoSoft-ExtraLightOblique.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-ExtraLightOblique.ttf
--------------------------------------------------------------------------------
/client/assets/Hando Soft/HandoSoft-HairLine.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-HairLine.ttf
--------------------------------------------------------------------------------
/client/assets/Hando Soft/HandoSoft-HairLineOblique.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-HairLineOblique.ttf
--------------------------------------------------------------------------------
/client/assets/Hando Soft/HandoSoft-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-Light.ttf
--------------------------------------------------------------------------------
/client/assets/Hando Soft/HandoSoft-LightOblique.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-LightOblique.ttf
--------------------------------------------------------------------------------
/client/assets/Hando Soft/HandoSoft-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-Medium.ttf
--------------------------------------------------------------------------------
/client/assets/Hando Soft/HandoSoft-MediumOblique.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-MediumOblique.ttf
--------------------------------------------------------------------------------
/client/assets/Hando Soft/HandoSoft-Oblique.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-Oblique.ttf
--------------------------------------------------------------------------------
/client/assets/Hando Soft/HandoSoft-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-Regular.ttf
--------------------------------------------------------------------------------
/client/assets/Hando Soft/HandoSoft-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-SemiBold.ttf
--------------------------------------------------------------------------------
/client/assets/Hando Soft/HandoSoft-SemiBoldOblique.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-SemiBoldOblique.ttf
--------------------------------------------------------------------------------
/client/assets/Hando Soft/HandoSoft-Thin.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-Thin.ttf
--------------------------------------------------------------------------------
/client/assets/Hando Soft/HandoSoft-ThinOblique.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Hando Soft/HandoSoft-ThinOblique.ttf
--------------------------------------------------------------------------------
/client/assets/Logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Logo.jpg
--------------------------------------------------------------------------------
/client/assets/Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Logo.png
--------------------------------------------------------------------------------
/client/assets/Readme Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/Readme Logo.png
--------------------------------------------------------------------------------
/client/assets/downloading.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/downloading.png
--------------------------------------------------------------------------------
/client/assets/loss_graphing_panel.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/loss_graphing_panel.gif
--------------------------------------------------------------------------------
/client/assets/model_architecture.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/model_architecture.gif
--------------------------------------------------------------------------------
/client/assets/readme logo (2).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/readme logo (2).png
--------------------------------------------------------------------------------
/client/assets/verticalNav-graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/verticalNav-graph.png
--------------------------------------------------------------------------------
/client/assets/verticalNav-home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/FlowSpace/dd717abdd33280823668d4f2b9d9ec6549c23e15/client/assets/verticalNav-home.png
--------------------------------------------------------------------------------
/client/components/AnalyticsTile.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '../style/AnalyticsTile.scss';
3 | import LossIcon from '../assets/Group 1.png';
4 | import BiasIcon from '../assets/Group 2.png';
5 | import WeightIcon from '../assets/Group 3.png';
6 | import ActivationIcon from '../assets/Group 4.png';
7 | import OptimizerIcon from '../assets/Group 6.png';
8 |
9 | const AnalyticsTile = ({ info }) => {
10 | const {
11 | type,
12 | value,
13 | description,
14 | color,
15 | boldName,
16 | } = info;
17 |
18 | return (
19 |
20 |
21 | {type === 'Loss' && (
22 |
23 | )}
24 | {type === 'Bias' && (
25 |
26 | )}
27 | {type === 'Max Weight' && (
28 |
29 | )}
30 | {type === 'Optimizer' && (
31 |
32 | )}
33 | {type === 'Activation' && (
34 |
35 | )}
36 |
37 |
38 | {value}
39 |
40 |
41 | {type}
42 |
43 |
44 | {boldName} {description}
45 |
46 |
47 | );
48 | };
49 |
50 | export default AnalyticsTile;
--------------------------------------------------------------------------------
/client/components/App.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '../style/App.scss';
3 | import VerticalTabNav from './VerticalTabNav.jsx';
4 |
5 |
6 | function App() {
7 | return (
8 |
13 | );
14 | }
15 |
16 | export default App;
17 |
--------------------------------------------------------------------------------
/client/components/BiasAnalytics.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import AnalyticsTile from './AnalyticsTile.jsx';
3 |
4 | const BiasAnalytics = ({ socket }) => {
5 |
6 | // hook for setting the value of biasData in state
7 | const [ biasData, setBiasData ] = useState();
8 |
9 | useEffect(() => {
10 | // listening for sentBiasData event from socket.io server
11 | socket.on('sentBiasData', (BiasData) => {
12 | if (!BiasData) {
13 | // display when data is not available
14 | setBiasData('Ø');
15 | return;
16 | }
17 | // setBiasData
18 | setBiasData(BiasData.toFixed(5).toString());
19 | });
20 | return () => {
21 | // stop listening for events
22 | socket.off('sentBiasData');
23 | }
24 | }, []);
25 |
26 | return (
27 |
34 | );
35 | };
36 |
37 | export default BiasAnalytics;
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/client/components/ExportButton.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import DownloadIcon from '../assets/downloading.png';
3 |
4 | // component for the export button icon on the tab component
5 | const ExportButton = (props) => {
6 | return (
7 |
8 |
9 |
10 | );
11 | }
12 |
13 | export default ExportButton;
--------------------------------------------------------------------------------
/client/components/GraphIcon.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import GraphIcon from '../assets/verticalNav-graph.png';
3 |
4 | // component for the graph icon on the tab component
5 | export default function GraphIconComponent() {
6 | return (
7 |
8 | )
9 | }
--------------------------------------------------------------------------------
/client/components/Logo.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import LogoFS from '../assets/Logo.png';
3 |
4 | // component for the FlowSpace Logo icon on the tab component
5 | export default function LogoFSComponent() {
6 | return (
7 |
8 | )
9 | }
--------------------------------------------------------------------------------
/client/components/LossPlot.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { scaleLinear, max, min, extent } from 'd3';
3 | import '../style/barChart.scss';
4 |
5 | const height = '350';
6 | const width = '950';
7 | const margin = {top: 20, right: 20, bottom: 50, left: 80}
8 |
9 | const ScatterPlot = (props) => {
10 | const [data, setData] = useState([])
11 |
12 | const innerHeight = height - margin.top - margin.bottom
13 | const innerWidth = width - margin.left - margin.right
14 |
15 | useEffect(() => {
16 | props.socket.emit('onClick');
17 | props.socket.on('sentLossDataPlot', (lossData) => {
18 | setData(lossData);
19 | });
20 | return () => {
21 | props.socket.off('sentLossDataPlot');
22 | }
23 | }, []);
24 |
25 | const yScale = scaleLinear()
26 | .domain([0, max(data, d => d.loss)])
27 | .range([innerHeight, 0])
28 |
29 | const xScale = scaleLinear()
30 | .domain([min(data, d => d.epoch), max(data, d => d.epoch)])
31 | .range([0, innerWidth])
32 | .nice();
33 |
34 |
35 | return (
36 |
37 |
38 |
39 |
40 | {xScale.ticks().map(tickValue => (
41 |
42 |
43 | {tickValue}
44 |
45 | ))}
46 | Epoch
47 |
48 | {yScale.ticks().map(tickValue => (
49 |
50 |
51 | {tickValue}
52 |
53 | ))}
54 | Loss
55 |
56 | { data.map(d => ) }
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | )
71 | }
72 |
73 | export default ScatterPlot;
74 |
--------------------------------------------------------------------------------
/client/components/OptimizerAnalytics.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import AnalyticsTile from './AnalyticsTile.jsx';
3 |
4 | const OptimizerAnalytics = ({ socket }) => {
5 |
6 | // hooks for setting the value of optimizerData and optimizerLearningRate in state
7 | const [ optimizerData, setOptimizerData ] = useState();
8 | const [ optimizerLearningRate, setOptimizerLearningRate ] = useState('');
9 |
10 | useEffect(() => {
11 | // listening for sentOptimizerData event
12 | socket.on('sentOptimizerData', (optimizerIterations, optimizerLearningRate) => {
13 | if (!optimizerIterations) {
14 | // display when data is not available
15 | setOptimizerData('Ø');
16 | return;
17 | }
18 | setOptimizerData(optimizerIterations);
19 | setOptimizerLearningRate(optimizerLearningRate);
20 | });
21 | return () => {
22 | // stop listening for events
23 | socket.off('sentOptimizerData');
24 | }
25 | }, []);
26 |
27 | return (
28 |
35 | );
36 | };
37 |
38 | export default OptimizerAnalytics;
--------------------------------------------------------------------------------
/client/components/ReactFlowNeuralNetwork.jsx:
--------------------------------------------------------------------------------
1 | // import { node } from '@tensorflow/tfjs-node';
2 | import React, { useCallback, useState, useEffect } from 'react';
3 | import ReactFlow, { useNodesState, useEdgesState, addEdge, ReactFlowProvider} from 'react-flow-renderer';
4 |
5 | // create Node class
6 | class Node {
7 | constructor(layerInfo, nodeInfo) {
8 | this.layerInfo = layerInfo;
9 | this.nodeInfo = nodeInfo;
10 | }
11 | }
12 |
13 | // style object for nodes
14 | const nodeStyle = {
15 | width: '5rem',
16 | height: '5rem',
17 | borderRadius: '50%',
18 | background: 'linear-gradient(to top, #DB4437, #EA8419, #F4B400)',
19 | color: '#fff',
20 | borderRadius: '50px',
21 | border: 'none',
22 | outline: 'none',
23 | cursor: 'pointer',
24 | boxShadow:'0 15px 30px rgb(179, 197, 234, .75)',
25 | }
26 |
27 | // declare arrays to hold mapped node and edge (connections) information
28 | let nodeInfo = [];
29 | let initialNodes = [];
30 | let initialEdges = [];
31 |
32 | // helper function to get the biggest layer height (most nodes) in the model
33 | const getBiggestLayerHeight = (layerInfo) => {
34 | // get the height of the input layer
35 | let height = 80 + (layerInfo[0].input_shape - 1) * 100;
36 |
37 | // loop through to find the largest height amongst the layers
38 | for (let keys in layerInfo) {
39 | height = Math.max(height, layerInfo[keys].layer_height_px);
40 | }
41 | // return the height
42 | return height;
43 | };
44 |
45 |
46 | /**
47 | * @name parseLayer function to automatically parse any model into nodes and edges
48 | * @param {object} layerInfo
49 | * @param {hook} setNodes for setting the node information in state
50 | * @param {hook} setEdges for setting the edge information in state
51 | * @param {array} allWeights all the weight data from the model. used for rendering line thicknesses
52 | * @returns
53 | */
54 | function parseLayer(layerInfo, setNodes, setEdges, allWeights) {
55 | // break if socket hasn't recieved layerInfo
56 | if (!layerInfo) return;
57 |
58 | // declare a varibale to store the biggest layer
59 | const maxHeight = getBiggestLayerHeight(layerInfo);
60 |
61 | // get the height of the input layer
62 | const inputHeight = 80 + (layerInfo[0].input_shape - 1) * 100;
63 |
64 | // populate input layer
65 | initialNodes = [];
66 | initialEdges = [];
67 |
68 | for (let i = 0; i < layerInfo[0].input_shape; i++) {
69 | let nodeInfo;
70 |
71 | // check if the input layer has the most nodes
72 | if (inputHeight === maxHeight) {
73 | // populate node information
74 | nodeInfo = {
75 | id: `input${i + 1}`,
76 | sourcePosition: 'right',
77 | type: 'input',
78 | position: { x: 0, y: 100 * i },
79 | className: 'clay',
80 | style: nodeStyle,
81 | };
82 | } else {
83 | // modifier to correctly place node along y-axis
84 | let yHeight = (maxHeight - inputHeight) / 2;
85 |
86 | // populate node information
87 | nodeInfo = {
88 | id: `input${i + 1}`,
89 | sourcePosition: 'right',
90 | type: 'input',
91 | position: { x: 0, y: 100 * i + yHeight },
92 | className: 'clay',
93 | style: nodeStyle,
94 | };
95 | }
96 |
97 | // create new instance of Node class with corresponing layer and node info
98 | const node = new Node(layerInfo[0], nodeInfo);
99 |
100 | // push new Node instance into intial nodes array
101 | initialNodes.push(node);
102 | }
103 |
104 | // populate hidden layers
105 | for (let keys in layerInfo) {
106 | for (let i = 0; i < layerInfo[keys].output_shape; i++) {
107 | let nodeInfo = {};
108 |
109 | // check to see if the node is in the output layer (effects x-position)
110 | if (layerInfo[keys].layer_number === Object.keys(layerInfo).length - 1) {
111 | // populate node information
112 | if (layerInfo[keys].layer_height_px === maxHeight) {
113 | nodeInfo = {
114 | id: `layer${Number(keys) + 1}-node${i + 1}`,
115 | targetPosition: 'left',
116 | type: 'output',
117 | position: { x: (Number(keys) + 1) * 300, y: 100 * i },
118 | style: nodeStyle,
119 | };
120 | } else {
121 | // modifier to correctly place node along y-axis
122 | let yHeight = (maxHeight - layerInfo[keys].layer_height_px) / 2;
123 | // populate node info
124 | nodeInfo = {
125 | id: `layer${Number(keys) + 1}-node${i + 1}`,
126 | targetPosition: 'left',
127 | type: 'output',
128 | position: { x: (Number(keys) + 1) * 300, y: 100 * i + yHeight },
129 | style: nodeStyle,
130 | };
131 | }
132 | } else {
133 | // populate node information for hidden layers
134 | if (layerInfo[keys].layer_height_px === maxHeight) {
135 | nodeInfo = {
136 | id: `layer${Number(keys) + 1}-node${i + 1}`,
137 | sourcePosition: 'right',
138 | targetPosition: 'left',
139 | position: { x: (Number(keys) + 1) * 300, y: 100 * i },
140 | style: nodeStyle,
141 | };
142 | } else {
143 | let yHeight = (maxHeight - layerInfo[keys].layer_height_px) / 2;
144 | nodeInfo = {
145 | id: `layer${Number(keys) + 1}-node${i + 1}`,
146 | sourcePosition: 'right',
147 | targetPosition: 'left',
148 | position: { x: (Number(keys) + 1) * 300, y: 100 * i + yHeight },
149 | style:nodeStyle,
150 | };
151 | }
152 | }
153 |
154 | // create new instance of Node class with corresponing layer and node info
155 | const node = new Node(layerInfo[keys], nodeInfo);
156 |
157 | // push new Node instance into intial nodes array
158 | initialNodes.push(node);
159 | }
160 | }
161 |
162 | // returs the shape of the next layer to the right
163 | const getNextLayerShape = (initialNodes, layerNumber) => {
164 | let nextLayerShape = 0;
165 | // loop through initial nodes until the first node of the next layer is identified
166 | for (let i = 0; i < initialNodes.length; i++) {
167 | if (initialNodes[i].layerInfo.layer_number === layerNumber + 1) {
168 | nextLayerShape = initialNodes[i].layerInfo.output_shape;
169 | break;
170 | }
171 | }
172 | return nextLayerShape;
173 | };
174 |
175 | // loop through nodes
176 | for (let nodeNum = 0; nodeNum < initialNodes.length; nodeNum++) {
177 | let currentLayerShape = 0;
178 | let nextLayerNumber = 0;
179 | let nextLayerShape = 0;
180 |
181 | // check if the current node is smaller than the first layer's input shape
182 | // i.e if current node is within the first layer
183 | if (nodeNum < layerInfo[0].input_shape) {
184 | currentLayerShape = Number(initialNodes[nodeNum].layerInfo.output_shape);
185 | nextLayerShape = currentLayerShape;
186 | nextLayerNumber = initialNodes[nodeNum].layerInfo.layer_number + 1;
187 | } else {
188 | // if the current node is in the hidden layers
189 | currentLayerShape = Number(initialNodes[nodeNum].layerInfo.output_shape);
190 | nextLayerShape = getNextLayerShape(initialNodes, initialNodes[nodeNum].layerInfo.layer_number);
191 | nextLayerNumber = initialNodes[nodeNum].layerInfo.layer_number + 2;
192 | }
193 | // used to track aand normalze the edge thickness based off Weight
194 | let counter = 0;
195 |
196 | // loop through entire layer and set edges
197 | for (let i = 0; i < nextLayerShape; i++) {
198 | // populate attributes of edge object
199 | const edge = {
200 | id: `${initialNodes[nodeNum].nodeInfo.id}-layer${nextLayerNumber}-node${i + 1}`,
201 | source: initialNodes[nodeNum].nodeInfo.id,
202 | type: 'simplebezier',
203 | target: `layer${nextLayerNumber}-node${i + 1}`,
204 | style: {
205 | stroke: '#F4B400',
206 | strokeWidth: allWeights ? allWeights[counter] : 1.5 // default thickness until weight data comes in
207 | },
208 | };
209 |
210 | counter++;
211 |
212 | initialEdges.push(edge);
213 | }
214 | }
215 |
216 | // set the nodeInfo arrayand setEdges array
217 | nodeInfo = initialNodes.map((x) => x.nodeInfo);
218 | setNodes(nodeInfo);
219 | setEdges(initialEdges);
220 | }
221 |
222 |
223 | // NeuralNetwork functional component
224 | const NeuralNetwork = (props) => {
225 | /*
226 | ReactFlow helper hooks to create new local state for nodes and edges
227 | Exposes a onNodesChange/onEdgesChange functions to be passed as props to the React Flow component
228 | */
229 | const [nodes, setNodes, onNodesChange] = useNodesState([]);
230 | const [edges, setEdges, onEdgesChange] = useEdgesState([]);
231 |
232 | // onConnect handler function to add edges
233 | const onConnect = useCallback(
234 | (params) => setEdges((els) => addEdge(params, els)),[]
235 | );
236 |
237 | // onLoad handler function to fit the ReactFlow canvas to the
238 | // neural network
239 | const onLoad = (reactFlowInstance) => {
240 | reactFlowInstance.fitView();
241 | }
242 |
243 | // lifecycle method for syncing component to external system (i.e. socket.io server)
244 | useEffect(() => {
245 | // listening for incomingData socket event from the socket-io server (see line 88 server.js)
246 | props.socket.on('incomingData', (data, allWeights) => {
247 | // parse data accordingly
248 | parseLayer(data, setNodes, setEdges, allWeights);
249 | });
250 | return () => {
251 | // no longer listening for socket events
252 | props.socket.off('incomingData');
253 | }
254 | }, []);
255 |
256 | return (
257 |
258 |
270 |
271 | );
272 | };
273 |
274 | export default NeuralNetwork;
275 |
--------------------------------------------------------------------------------
/client/components/VerticalTabNav.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import PropTypes from 'prop-types';
3 | import Tabs from '@mui/material/Tabs';
4 | import Tab from '@mui/material/Tab';
5 | import Box from '@mui/material/Box';
6 | import { createTheme } from '@mui/material/styles';
7 | import Logo from './Logo.jsx';
8 | import GraphIcon from './GraphIcon.jsx';
9 | import NeuralNetwork from './ReactFlowNeuralNetwork.jsx';
10 | import socketIO from 'socket.io-client';
11 | import LossPlot from './LossPlot.jsx';
12 | import LossAnalytics from './LossAnalytics.jsx';
13 | import WeightAnalytics from './WeightAnalytics.jsx';
14 | import ExportButton from './ExportButton.jsx';
15 | import BiasAnalytics from './BiasAnalytics.jsx';
16 | import OptimizerAnalytics from './OptimizerAnalytics.jsx';
17 |
18 | // connection to socket.io server on FlowSpace's backend
19 | const socket = socketIO.connect('http://localhost:3333');
20 |
21 | // override MUI default theme to match FlowSpace UI
22 | const theme = createTheme({
23 | palette: {
24 | primary: {
25 | light: '#757ce8',
26 | main: '#3f50b5',
27 | dark: '#002884',
28 | contrastText: '#fff',
29 | },
30 | secondary: {
31 | light: '#ff7961',
32 | main: '#f44336',
33 | dark: '#ba000d',
34 | contrastText: '#000',
35 | },
36 | },
37 | });
38 |
39 | // event handler to emit socket information on click
40 | function clickEvent() {
41 | socket.emit("onClick");
42 | }
43 |
44 | // fuctional component to place the content of the respective tab
45 | function TabPanel(props) {
46 | /* Default props
47 | * @props children (node): component similar to the table row.
48 | * @props value (Object): The current panel, for toggling hidden/not hidden
49 | * @props index (number): The number of the panel order from 0+
50 | */
51 | const { children, value, index, ...other } = props;
52 |
53 | return (
54 |
61 | {/* conditionally render panel only when it's index is strictly equal to value */}
62 | {value === index && (
63 |
64 | {children}
65 |
66 | )}
67 |
68 | );
69 | }
70 |
71 | // set the typing and requirements for the props passed to TabPanel components
72 | TabPanel.propTypes = {
73 | children: PropTypes.node,
74 | index: PropTypes.number.isRequired,
75 | value: PropTypes.number.isRequired,
76 | };
77 |
78 | // function to toggle the component's id attribute based on the index of the tab panel
79 | function a11yProps(index) {
80 | return {
81 | id: `vertical-tab-${index}`,
82 | 'aria-controls': `vertical-tabpanel-${index}`,
83 | };
84 | }
85 |
86 | // functional vertical tab component
87 | export default function VerticalTabs() {
88 | // hook to set the value variable
89 | const [value, setValue] = useState(0);
90 |
91 | // event handler to update value
92 | const handleChange = (event, newValue) => {
93 | setValue(newValue);
94 | };
95 |
96 | // lifecycle method for syncing component to external system (i.e. socket.io server)
97 | useEffect(() => clickEvent(), []);
98 |
99 | return (
100 |
110 |
111 | } style={{ textAlign:'center'}} {...a11yProps(0)} onClick={clickEvent}/>
112 | } style={{ textAlign:'center' }} {...a11yProps(1)} onClick={clickEvent}/>
113 |
114 |
115 |
116 |
117 |
Welcome,
118 |
Model Architecture
119 |
120 |
125 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 | );
151 | }
152 |
--------------------------------------------------------------------------------
/client/components/lossAnalytics.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import AnalyticsTile from './AnalyticsTile.jsx';
3 |
4 | const LossAnalytics = ({ socket }) => {
5 |
6 | // hooks for setting the value of lossData and lossMethod in state
7 | const [ lossData, setLossData ] = useState();
8 | const [ lossMethod, setLossMethod ] = useState('');
9 |
10 |
11 | useEffect(() => {
12 | // listening for sentLossDataAnalytics event from socket.io server
13 | socket.on('sentLossDataAnalytics', (lossData, lossMethod) => {
14 | if (!lossData.length) {
15 | // display when data is not available
16 | setLossData('Ø');
17 | return;
18 | }
19 | // set data accordingly
20 | setLossData(lossData[lossData.length - 1].loss.toFixed(6).toString());
21 | setLossMethod(lossMethod);
22 | });
23 | return () => {
24 | // stop listening for events
25 | socket.off('sentLossDataAnalytics');
26 | }
27 | }, []);
28 |
29 | return (
30 |
38 | );
39 | };
40 |
41 | export default LossAnalytics;
--------------------------------------------------------------------------------
/client/components/weightAnalytics.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import AnalyticsTile from './AnalyticsTile.jsx';
3 |
4 | const WeightAnalytics = ({ socket }) => {
5 |
6 | // hook for setting the value of weightData in state
7 | const [ weightData, setWeightData ] = useState();
8 |
9 | useEffect(() => {
10 | // listening for sentWeightData event from socket.io server
11 | socket.on('sentWeightData', (WeightData) => {
12 | if (!WeightData) {
13 | // display when data is not available
14 | setWeightData('Ø');
15 | return;
16 | }
17 | // setWeightData
18 | setWeightData(WeightData.toFixed(5).toString());
19 | });
20 | return () => {
21 | // stop listening for events
22 | socket.off('sentWeightData');
23 | }
24 | }, []);
25 |
26 | return (
27 |
34 | );
35 | };
36 |
37 | export default WeightAnalytics;
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
FlowSpace
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/client/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import './style/index.scss';
4 | import App from './components/App.jsx';
5 |
6 | const root = ReactDOM.createRoot(document.getElementById('root'));
7 | root.render(
8 |
9 |
10 |
11 | );
12 |
--------------------------------------------------------------------------------
/client/style/AnalyticsTile.scss:
--------------------------------------------------------------------------------
1 | /* create custom font face for titles/larger text*/
2 | @font-face {
3 | font-family: 'Aeonik';
4 | src: local('Aeonik-Bold'), url('../assets/Aeonik/Aeonik-Bold.otf') format('otf');
5 | src: local('Aeonik-Regular'), url('../assets/Aeonik/Aeonik-Regular.otf') format('otf');
6 | }
7 |
8 | /* create custom font face for subtitles*/
9 | @font-face {
10 | font-family: 'HandoSoft';
11 | src: local('HandoSoft-Regular'), url('../assets/Hando\ Soft/HandoSoft-Regular.ttf') format('ttf');
12 | src: local('HandoSoft-Medium'), url('../assets/Hando\ Soft/HandoSoft-Medium.ttf') format('ttf');
13 | src: local('HandoSoft-Black'), url('../assets/Hando\ Soft/HandoSoft-Black.ttf') format('ttf');
14 | }
15 |
16 | /* make each tile a column flexbox */
17 | .Analytics-Tile {
18 | display: flex;
19 | flex-direction: column;
20 | /* center align along main axis, vertically*/
21 | justify-content: center;
22 | /* center align along crosss axis, horizontally */
23 | align-items: center;
24 | margin-right: 2rem;
25 |
26 | padding: 1rem;
27 | background-color: #fff;
28 | border-radius: 30px;
29 | /* claymorphism styling */
30 | backdrop-filter: blur(5px);
31 | background-color: rgba(255, 255, 255, 1);
32 | box-shadow: 0 10px 20px rgb(179, 197, 234, .75),
33 | inset 0px 0px 0px 0px rgba(145, 192, 255, 0.75),
34 | inset -4px -4px 8px 0px rgba(145, 193, 255, 0.30),
35 | inset 0px 10px 20px 0px rgb(255, 255, 255);
36 | }
37 |
38 | /* uniform styling and sizing of icons */
39 | img {
40 | width: 50px;
41 | height: auto;
42 | margin-bottom: 1rem;
43 | }
44 |
45 | /* text styling for the displayed live data */
46 | .tile-value {
47 | font-size: 50px;
48 | margin-top: .5rem;
49 | font-family: HandoSoft-Black;
50 | }
51 |
52 | /* text styling for the title of the tile*/
53 | .tile-title {
54 | font-size: 17px;
55 | font-weight: bold;
56 | text-transform: uppercase;
57 | margin-top: 5px;
58 | color: black;
59 | }
60 |
61 | /* text styling for the description */
62 | .tile-description {
63 | font-family: Aeonik-Regular;
64 | text-align: center;
65 | width: 235px;
66 | font-size: 15px;
67 | margin-top: 5px;
68 | }
69 |
70 |
--------------------------------------------------------------------------------
/client/style/App.scss:
--------------------------------------------------------------------------------
1 | /* create custom font face for titles/larger text*/
2 | @font-face {
3 | font-family: 'HandoSoft';
4 | src: local('HandoSoft-Regular'), url('../assets/Hando\ Soft/HandoSoft-Regular.ttf') format('ttf');
5 | src: local('HandoSoft-Medium'), url('../assets/Hando\ Soft/HandoSoft-Medium.ttf') format('ttf');
6 | }
7 |
8 | /* create custom font face for subtitles*/
9 | @font-face {
10 | font-family: 'Aeonik';
11 | src: local('Aeonik-Bold'), url('../assets/Aeonik/Aeonik-Bold.otf') format('otf');
12 | src: local('Aeonik-Regular'), url('../assets/Aeonik/Aeonik-Regular.otf') format('otf');
13 | }
14 |
15 | /* styling for overall App.jsx component*/
16 | .App {
17 | /* default width and height */
18 | width: 100vw;
19 | height: 100vh;
20 |
21 | /* set min width/height for window resizing*/
22 | min-width: 1000px;
23 | min-height: 800px;
24 |
25 | /* set lightblue gradient as background*/
26 | background-image: linear-gradient(
27 | 135deg,
28 | hsl(0deg 0% 100%) 0%,
29 | hsl(223deg 57% 98%) 13%,
30 | hsl(223deg 57% 96%) 27%,
31 | hsl(222deg 57% 94%) 40%,
32 | hsl(222deg 57% 92%) 52%,
33 | hsl(222deg 57% 90%) 64%,
34 | hsl(221deg 57% 87%) 75%,
35 | hsl(221deg 57% 85%) 85%,
36 | hsl(221deg 57% 83%) 93%,
37 | hsl(220deg 57% 81%) 100%
38 | );
39 | /* flexbox --> direction agnostic --> increase responsivess to page resizing*/
40 | display: flex;
41 | /* main axis as vertical axis*/
42 | flex-direction: column;
43 | /* center along main axis, vertically*/
44 | justify-content: center;
45 | /* center along cross-axis, horizontally*/
46 | align-items: center;
47 | }
48 |
49 |
50 | .Dashboard {
51 | /* 90% of parent div, App*/
52 | width: 90%;
53 | height: 90%;
54 |
55 | min-width: 950px;
56 | /* round corners to fit claymorphism styling */
57 | border-radius: 30px;
58 | /* background of dashboard is white*/
59 | background-color: #FAFBFF;
60 | /* shadow to make dashhboard appear as if hovering*/
61 | box-shadow: 0 10px 20px rgb(179, 197, 234, .75);
62 | }
63 |
64 | .react-flow-rectangle {
65 | width: 99.5%;
66 | height: 100%;
67 | border-radius: 30px;
68 | backdrop-filter: blur(5px);
69 | background-color: rgba(255, 255, 255, 1);
70 | /* claymorphism styling */
71 | box-shadow: 0 10px 20px rgb(179, 197, 234, .75),
72 | inset 0px 0px 0px 0px rgba(145, 192, 255, 0.75),
73 | inset -4px -4px 8px 0px rgba(145, 193, 255, 0.30),
74 | inset 0px 10px 20px 0px rgb(255, 255, 255);
75 | }
76 |
77 | /* styling for "Welcome" */
78 | .analytics-header p {
79 | color: rgb(164, 162, 162);
80 | font-family: HandoSoft-Medium;
81 | font-size: 15px;
82 | margin-left: 0rem;
83 | margin-bottom: 0px;
84 | margin-top: 0px;
85 | }
86 |
87 | /* Styling for "Model Architecture" */
88 | .analytics-header h2 {
89 | margin-top: 0px;
90 | color: black;
91 | font-family: Aeonik-Bold;
92 | font-size: 20px;
93 | margin-left: 0rem;
94 | }
95 |
96 | /* graph panel wrapper to instantiate a flexbox with columns*/
97 | .graphPanelWrapper {
98 | display: flex;
99 | flex-direction: column;
100 | }
101 |
102 | /* styling for "At A Glance" */
103 | .analytics-overview-header h2 {
104 | color: black;
105 | font-family: Aeonik-Bold;
106 | font-size: 20px;
107 | margin-left: 0rem;
108 | }
109 |
110 | /* custom margin spacing for overview header on main tab*/
111 | #front-overview-header {
112 | margin-top: .3rem;
113 | margin-bottom: .3rem;
114 | }
115 |
116 | /* custom margin spacing for overview header on graph tab*/
117 | #graph-overview-header {
118 | margin-top: 1rem;
119 | }
120 |
121 | /* due to graph tab being a flexbox, overwrite h2 display attribute */
122 | #graph-overview-header h2 {
123 | display: inline;
124 | }
125 |
126 | /* horizontal scrolling div with analytics tiles */
127 | .analytics-tiles {
128 | height:42%;
129 | width: 102.4%;
130 | display: flex;
131 | flex-direction: row;
132 | flex-wrap: nowrap;
133 | overflow-x: auto;
134 | box-sizing: border-box;
135 | padding-left: 1rem;
136 |
137 | .tile {
138 | flex: 0 0 auto;
139 | }
140 | }
141 |
142 | /*
143 | graph tiles need more padding due to size difference betwen ReactFlow
144 | component and D3 graph component
145 | */
146 | #graph-tiles {
147 | padding-block: 25px;
148 | }
149 |
150 | /* remove horizontal scroll bars from analytics tile div*/
151 | .analytics-tiles::-webkit-scrollbar {
152 | display: none;
153 | }
154 |
155 | /* hide the reactflow watermark/attribution*/
156 | .react-flow__attribution {
157 | visibility: hidden;
158 | }
159 |
160 | /* top and bottom margin on icons set to 0, left and right margins on auto*/
161 | .tabIcon {
162 | margin: 0 auto;
163 | }
164 |
165 |
166 |
167 |
--------------------------------------------------------------------------------
/client/style/barChart.scss:
--------------------------------------------------------------------------------
1 | /* create custom font face for titles/larger text*/
2 | @font-face {
3 | font-family: 'Aeonik';
4 | src: local('Aeonik-Bold'), url('../assets/Aeonik/Aeonik-Bold.otf') format('otf');
5 | src: local('Aeonik-Regular'), url('../assets/Aeonik/Aeonik-Regular.otf') format('otf');
6 | }
7 |
8 | /* create custom font face for subtitles*/
9 | @font-face {
10 | font-family: 'HandoSoft';
11 | src: local('HandoSoft-Regular'), url('../assets/Hando\ Soft/HandoSoft-Regular.ttf') format('ttf');
12 | src: local('HandoSoft-Medium'), url('../assets/Hando\ Soft/HandoSoft-Medium.ttf') format('ttf');
13 | }
14 |
15 | /* add stroke to lines */
16 | .tick line {
17 | stroke: #C0C0BB
18 | }
19 |
20 | .tick text {
21 | fill: #635F5D;
22 | }
23 |
24 | .mark {
25 | fill: url(#MarkGradient);
26 | -webkit-filter: drop-shadow( 3px 3px 2px rgb(179, 197, 234, .75));
27 | filter: drop-shadow( 3px 3px 2px rgb(179, 197, 234, .75));
28 | }
29 |
30 | .xAxisLabel {
31 | font-size: 1rem;
32 | fill: #635F5D;
33 | }
34 |
35 | .yAxisLabel {
36 | transform: rotate(-90deg);
37 | font-size: 1rem;
38 | font-family: HandoSoft-Medium;
39 | fill: #635F5D;
40 | }
41 |
42 | .d3Graph {
43 | padding: 1rem;
44 | background-color: #fff;
45 | border-radius: 30px;
46 | backdrop-filter: blur(5px);
47 | background-color: rgba(255, 255, 255, 1);
48 | box-shadow: 0 10px 20px rgb(179, 197, 234, .75),
49 | inset 0px 0px 0px 0px rgba(145, 192, 255, 0.75),
50 | inset -4px -4px 8px 0px rgba(145, 193, 255, 0.30),
51 | inset 0px 10px 20px 0px rgb(255, 255, 255);
52 |
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/client/style/index.scss:
--------------------------------------------------------------------------------
1 | /* fallbacks if borwser does not support custom fonts*/
2 | body {
3 | margin: 0;
4 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
5 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
6 | sans-serif;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | }
10 |
11 | /* setting fallback fonts if browser doesn't support custom fonts */
12 | code {
13 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
14 | monospace;
15 | }
16 |
--------------------------------------------------------------------------------
/handshake.js:
--------------------------------------------------------------------------------
1 | // connection to DevClient side
2 |
3 | //imports socketIO client with connection
4 | import socketIO from 'socket.io-client';
5 |
6 | const socket = socketIO.connect('http://localhost:3333');
7 |
8 | export class HandShake {
9 | constructor(model) {
10 | this.model = model;
11 | this.socket = socket;
12 | this.socket.emit('modelData', this.model);
13 | this.lossCallback = this.lossCallback.bind(this);
14 | }
15 |
16 | lossCallback(epoch, log) {
17 | let x = this.model.layers
18 | const allWeights = [];
19 | const allAbsMax = [];
20 | const allBiasAbsMax = [];
21 |
22 | // Let's get the max of each layer
23 | for (let i = 0; i < x.length; i++) {
24 | // This gets the weights in each layer and returns an array of weights
25 | let arr = x[i].getWeights()[0].dataSync()
26 | // allWeights is an array that contains the weights of all the layers
27 | // allWeights is used to help in rendering the connection thickness
28 | allWeights.push(...Array.from(arr))
29 | // We then find the max and min values in the array
30 | let max = Math.max(...arr)
31 | let min = Math.min(...arr)
32 | // The maximum absolute value is then stored in allAbsMax
33 | if(Math.abs(max) < Math.abs(min)) {
34 | allAbsMax.push(min)
35 | } else {
36 | allAbsMax.push(max)
37 | }
38 | }
39 |
40 | for (let i = 0; i < x.length; i++) {
41 | // This gets the weights in each layer and returns an array of weights
42 | let biasArr = x[i].getWeights()[1].dataSync()
43 | // We then find the max and min values in the array
44 | let max = Math.max(...biasArr)
45 | let min = Math.min(...biasArr)
46 | // The maximum absolute value is then stored in allAbsMax
47 | if(Math.abs(max) < Math.abs(min)) {
48 | allBiasAbsMax.push(min)
49 | } else {
50 | allBiasAbsMax.push(max)
51 | }
52 | }
53 |
54 | const biasResult = allBiasAbsMax.reduce(
55 | (prev, current) => Math.abs(current) > Math.abs(prev) ? current : prev
56 | , 0);
57 |
58 | // Result will be the max of node weights of all the layers
59 | const result = allAbsMax.reduce(
60 | (prev, current) => Math.abs(current) > Math.abs(prev) ? current : prev
61 | , 0);
62 |
63 | // find the absolute minimum to normalize
64 | let minimum = Infinity;
65 | for(let weight = 0; weight < allWeights.length; weight++) {
66 | minimum = Math.min(minimum, Math.abs(allWeights[weight]))
67 | }
68 | const normalizedData = []
69 | for(let weight = 0; weight < allWeights.length; weight++) {
70 | // normalized the data between 0.8 and 10
71 | let normalizedWeight = 0.8 + ( ((Math.abs(allWeights[weight]) - minimum) * (10 - 0.8)) / Math.abs(result) - minimum)
72 | normalizedData.push(normalizedWeight)
73 | }
74 | socket.emit('modelInfo', this.model.loss, this.model.optimizer, biasResult, result, { epoch, loss: log.loss } )
75 | socket.emit('modelData', this.model, normalizedData)
76 |
77 | }
78 |
79 | }
--------------------------------------------------------------------------------
/handshake.test.js:
--------------------------------------------------------------------------------
1 | import { HandShake } from "./handshake";
2 | import express from "express";
3 | const app = express();
4 | import http from "http";
5 | const server = http.createServer(app);
6 | import { Server } from "socket.io";
7 | import * as tf from "@tensorflow/tfjs";
8 |
9 |
10 | describe("testing functionality of socket.io in handshake class", () => {
11 | let handshakeInstance;
12 | let io;
13 | let model;
14 | let serverSocket;
15 |
16 | beforeAll(() => {
17 | io = new Server(server);
18 | io.on('connection', (socket) => {
19 | serverSocket = socket;
20 | });
21 | model = tf.sequential();
22 | model.add(
23 | tf.layers.dense({
24 | units: 1,
25 | inputShape: [1],
26 | activation: "linear",
27 | useBias: true,
28 | })
29 | );
30 | model.add(tf.layers.dense({ units: 10, activation: "linear", useBias: true }));
31 | model.add(tf.layers.dense({ units: 4, activation: "linear", useBias: true }));
32 | model.add(tf.layers.dense({ units: 1, activation: "linear", useBias: true }));
33 | const optimizer = tf.train.sgd(0.1);
34 | model.compile({ optimizer, loss: "meanSquaredError" });
35 | });
36 |
37 | afterAll(() => {
38 | io.close();
39 | handshakeInstance.socket.close();
40 | });
41 |
42 |
43 | test("checks initial data", (done) => {
44 | io.on("connection", () => {
45 | serverSocket.on("modelData", (modelData, allWeights) => {
46 | modelData = JSON.parse(modelData);
47 | expect(typeof modelData).toBe('object');
48 | expect(modelData.class_name).toBe('Sequential');
49 | done();
50 | });
51 | });
52 | handshakeInstance = new HandShake(model);
53 | });
54 |
55 | test("checks model type from lossCallback", (done) => {
56 | serverSocket.on("modelData", (modelData, allWeights) => {
57 | modelData = JSON.parse(modelData);
58 | expect(modelData.class_name).toBe('Sequential');
59 | done();
60 | });
61 | handshakeInstance.lossCallback(1, {loss: 4});
62 | });
63 |
64 | test("checks number of weight data points from lossCallback", (done) => {
65 | serverSocket.on("modelData", (modelData, allWeights) => {
66 | expect(allWeights.length).toBe(55);
67 | done();
68 | });
69 | handshakeInstance.lossCallback(1, {loss: 4});
70 | });
71 |
72 | test("checks normalization of weight data from lossCallback is between 0.8 and 10", (done) => {
73 | serverSocket.on("modelData", (modelData, allWeights) => {
74 | expect(Math.round(Math.max(...allWeights))).toBe(10);
75 | expect(Math.round(Math.min(...allWeights) * 10)).toBe(8);
76 | done();
77 | });
78 | handshakeInstance.lossCallback(1, {loss: 4});
79 | });
80 |
81 | test("checks correct data types from lossCallback", (done) => {
82 | serverSocket.on('modelInfo', (loss, optimizer, biasResult, result, epochLossData) => {
83 | expect(loss).toBe('meanSquaredError');
84 | expect(typeof biasResult).toBe('number');
85 | expect(typeof result).toBe('number');
86 | done();
87 | });
88 | handshakeInstance.lossCallback(4, {loss: 7});
89 | });
90 |
91 | test("checks model learning rate from lossCallback", (done) => {
92 | serverSocket.on('modelInfo', (loss, optimizer, biasResult, result, epochLossData) => {
93 | expect(optimizer.learningRate).toBe(0.1);
94 | done();
95 | });
96 | handshakeInstance.lossCallback(4, {loss: 7});
97 | });
98 |
99 | test("checks epoch loss data from lossCallback", (done) => {
100 | serverSocket.on('modelInfo', (loss, optimizer, biasResult, result, epochLossData) => {
101 | expect(epochLossData.epoch).toBe(4);
102 | expect(epochLossData.loss).toBe(7);
103 | done();
104 | });
105 | handshakeInstance.lossCallback(4, {loss: 7});
106 | });
107 |
108 | server.listen(3333);
109 | })
110 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "flowspace.js",
3 | "version": "0.0.1",
4 | "description": "a data visualization tool for TensorFlow.js",
5 | "main": "handshake.js",
6 | "type": "module",
7 | "scripts": {
8 | "test": "NODE_OPTIONS='--experimental-vm-modules' jest",
9 | "start": "nodemon ./server.js",
10 | "dev": "concurrently \"webpack serve --hot --progress --color\" \"nodemon ./server.js\"",
11 | "format": "prettier --write \"***/**/*.{js,jsx}\"",
12 | "build": "webpack"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "git+https://github.com/oslabs-beta/FlowSpace.git"
17 | },
18 | "author": "Mark Alexander, Saif Beiruty, Laurence Diarra, Mike Oakes, and Sabre Nguyen",
19 | "license": "MIT",
20 | "bugs": {
21 | "url": "https://github.com/oslabs-beta/FlowSpace/issues"
22 | },
23 | "homepage": "https://github.com/oslabs-beta/FlowSpace#readme",
24 | "dependencies": {
25 | "@babel/preset-env": "^7.18.10",
26 | "@babel/preset-react": "^7.18.6",
27 | "@emotion/react": "^11.10.4",
28 | "@emotion/styled": "^11.10.4",
29 | "@mui/material": "^5.10.3",
30 | "@tensorflow/tfjs-node": "^3.19.0",
31 | "concurrently": "^7.3.0",
32 | "d3": "^7.6.1",
33 | "express": "^4.18.1",
34 | "nodemon": "^2.0.20",
35 | "prettier": "^2.7.1",
36 | "react": "^18.2.0",
37 | "react-dom": "^18.2.0",
38 | "react-flow-renderer": "^10.3.16",
39 | "react-router-dom": "^6.3.0",
40 | "react-scripts": "5.0.1",
41 | "sass-loader": "^13.0.2",
42 | "socket.io": "^4.5.1",
43 | "socket.io-client": "^4.5.1",
44 | "url-loader": "^4.1.1",
45 | "web-vitals": "^2.1.4"
46 | },
47 | "bin": {
48 | "flowspace": "./server.js"
49 | },
50 | "devDependencies": {
51 | "jest": "^29.2.2",
52 | "svg-inline-loader": "^0.8.2",
53 | "webpack": "^5.74.0",
54 | "webpack-cli": "^4.10.0"
55 | },
56 | "jest": {
57 | "testEnvironment": "jest-environment-node",
58 | "transform": {}
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import express from 'express';
4 | const app = express();
5 | import http from 'http';
6 | const server = http.createServer(app);
7 | import { Server } from 'socket.io';
8 | import path from 'path';
9 | import { fileURLToPath } from 'url';
10 |
11 | const __filename = fileURLToPath(import.meta.url);
12 |
13 | // socket-io server
14 | const io = new Server(server, {
15 | cors: {
16 | origin: ["http://localhost:8080", "http://localhost:8081"],
17 | },
18 | });
19 |
20 | app.get("/", (req, res) => {
21 | res.sendFile(path.dirname(__filename) + "/build/index.html");
22 | });
23 |
24 | app.get("/bundle.js", (req, res) => {
25 | res.sendFile(path.dirname(__filename) + "/build/bundle.js");
26 | });
27 |
28 | app.get("/export", (req, res) => {
29 | res.set({
30 | 'Content-Type': 'application/json-attachment',
31 | 'content-disposition': 'attachment; filename="export.json"'
32 | });
33 | res.send(JSON.stringify(lossData));
34 | });
35 |
36 | // model must be doubly parsed prior to using this function
37 | const parseModel = (model) => {
38 | model = JSON.parse(model);
39 |
40 | let layer = {};
41 | let input_shape;
42 | let output_shape;
43 | let params;
44 |
45 | for (let i = 0; i < model.config.layers.length; i++) {
46 | // check if it's the first layer or not to set input shape
47 | model.config.layers[i].config.batch_input_shape
48 | ? (input_shape = model.config.layers[i].config.batch_input_shape[1])
49 | : (input_shape = model.config.layers[i - 1].config.units);
50 |
51 | // set output shape
52 | output_shape = model.config.layers[i].config.units;
53 |
54 | // calculate layer height
55 | const layer_height_px = 80 + (output_shape - 1) * 100;
56 |
57 | // calculate connections
58 | params = input_shape * output_shape + output_shape;
59 |
60 | layer[i] = {
61 | layer_number: i,
62 | layer_type: model.config.layers[i].config.name.includes("dense")
63 | ? "DENSE"
64 | : "NOT DENSE",
65 | input_shape,
66 | output_shape,
67 | layer_height_px,
68 | params,
69 | };
70 | }
71 | return layer;
72 | };
73 |
74 | // session data: every connection between flowspace (socket.io server) and
75 | // dev's environment (socket.io client) temporarily store data in these variables
76 | // to be sent to Flowspaces's fronted (socket.io client)
77 | let lossData = [];
78 | let weightData;
79 | let lossMethodHolder;
80 | let savedModel;
81 | const allWeightData = [];
82 | let biasData;
83 | let optimizerIterations;
84 | let optimizerLearingRate;
85 |
86 | // connect to socket-io client on dev's end
87 | io.on("connection", (socket) => {
88 | console.log("client connected");
89 |
90 | // modelData event, means our socket.io-server is listening for this event from dev's
91 | // socket.io client via the Handshake
92 | socket.on("modelData", (data, allWeights) => {
93 | const d = parseModel(data);
94 | savedModel = parseModel(data);
95 | allWeightData.push(allWeights);
96 | io.sockets.emit("incomingData", d, allWeights);
97 | });
98 |
99 | // modelData event, means our socket.io-server is listening for this event from dev's
100 | // socket.io client via the Handshake
101 | socket.on("modelInfo", (lossMethod, optimizer, maxBias, maxWeight, loss) => {
102 | if (loss.epoch === 0) {
103 | lossData = [];
104 | }
105 | lossData.push(loss);
106 | io.sockets.emit("sentOptimizerData", optimizer.iterations_, optimizer.learningRate);
107 | io.sockets.emit("sentLossDataPlot", lossData);
108 | io.sockets.emit("sentLossDataAnalytics", lossData, lossMethod);
109 | io.sockets.emit("sentWeightData", maxWeight);
110 | io.sockets.emit('sentBiasData', maxBias)
111 | weightData = maxWeight;
112 | biasData = maxBias;
113 | optimizerIterations = optimizer.iterations_;
114 | optimizerLearingRate = optimizer.learningRate;
115 | lossMethodHolder = lossMethod
116 | });
117 |
118 | // onClick vent, means our socket.io-server is listening for this event from dev's
119 | // socket.io client via the Handshake
120 | socket.on("onClick", () => {
121 | io.sockets.emit("incomingData", savedModel, allWeightData[allWeightData.length - 1]);
122 | io.sockets.emit("sentLossDataPlot", lossData);
123 | io.sockets.emit("sentLossDataAnalytics", lossData, lossMethodHolder);
124 | io.sockets.emit("sentWeightData", weightData);
125 | io.sockets.emit("sentBiasData", biasData);
126 | io.sockets.emit("sentOptimizerData", optimizerIterations, optimizerLearingRate);
127 | });
128 |
129 | // diconnect vent, means our socket.io-server is listening for this event from dev's
130 | // socket.io client via the Handshake
131 | socket.on("disconnect", () => {
132 | console.log("client disconnected");
133 | });
134 | });
135 |
136 | server.listen(3333, () => {
137 | console.log("Server listening on 3333");
138 | });
139 |
140 |
141 |
--------------------------------------------------------------------------------
/server/socketController.js:
--------------------------------------------------------------------------------
1 | const socketController = {};
2 |
3 | function parseLayer(layerInfo) {
4 | // input layer
5 | for (let i = 0; i < layerInfo[0].input_shape; i++) {
6 | // populate node information
7 | const nodeInfo = {
8 | id: `input${i + 1}`,
9 | sourcePosition: 'right',
10 | type: 'input',
11 | data: { label: `Input-${i + 1}` },
12 | position: { x: 0, y: 100 * i },
13 | className: 'clay',
14 | style: {
15 | width: '5rem',
16 | height: '5rem',
17 | borderRadius: '50%',
18 | fontWeight: 'bold',
19 | border: 'none',
20 | padding: '2rem .5rem',
21 | fontFamily: 'inherit',
22 | backgrounBlendMode: 'multiply',
23 | color: 'rgb(235, 234, 234)',
24 | background: 'linear-gradient(225deg, #181818, #2e2e2e)',
25 | boxShadow: '5px 5px 10px #191919, 5px -5px 10px #292929',
26 | },
27 | };
28 |
29 | // create new instance of Node class with corresponing layer and node info
30 | const node = new Node(layerInfo[0], nodeInfo);
31 |
32 | // push new Node instance into intial nodes array
33 | initialNodes.push(node);
34 | }
35 |
36 | // hidden layers
37 | for (let keys in layerInfo) {
38 | for (let i = 0; i < layerInfo[keys].output_shape; i++) {
39 | let nodeInfo = {};
40 |
41 | // check to see if the node is in the output layer
42 | if (layerInfo[keys].layer_number === Object.keys(layerInfo).length - 1) {
43 | // populate node information
44 | nodeInfo = {
45 | id: `layer${Number(keys) + 1}-node${i + 1}`,
46 | targetPosition: 'left',
47 | type: 'output',
48 | data: { label: `Output-${i + 1}` }, //`Layer${Number(keys)+1}-Node-${i+1}`
49 | position: { x: (Number(keys) + 1) * 300, y: 100 * i },
50 | style: {
51 | width: '5rem',
52 | height: '5rem',
53 | borderRadius: '50%',
54 | fontWeight: 'bold',
55 | border: 'none',
56 | padding: '2rem .5rem',
57 | fontFamily: 'inherit',
58 | backgrounBlendMode: 'multiply',
59 | color: 'rgb(235, 234, 234)',
60 | background: 'linear-gradient(225deg, #181818, #2e2e2e)',
61 | boxShadow: '5px 5px 10px #191919, 5px -5px 10px #292929',
62 | },
63 | };
64 | } else {
65 | // populate node information
66 | nodeInfo = {
67 | id: `layer${Number(keys) + 1}-node${i + 1}`,
68 | sourcePosition: 'right',
69 | targetPosition: 'left',
70 | data: { label: `Layer${Number(keys) + 1}-Node-${i + 1}` },
71 | position: { x: (Number(keys) + 1) * 300, y: 100 * i },
72 | style: {
73 | width: '5rem',
74 | height: '5rem',
75 | borderRadius: '50%',
76 | fontWeight: 'bold',
77 | border: 'none',
78 | padding: '2rem .5rem',
79 | fontFamily: 'inherit',
80 | backgrounBlendMode: 'multiply',
81 | color: 'rgb(235, 234, 234)',
82 | background: 'linear-gradient(225deg, #181818, #2e2e2e)',
83 | boxShadow: '5px 5px 10px #191919, 5px -5px 10px #292929',
84 | },
85 | };
86 | }
87 |
88 | // create new instance of Node class with corresponing layer and node info
89 | const node = new Node(layerInfo[keys], nodeInfo);
90 |
91 | // push new Node instance into intial nodes array
92 | initialNodes.push(node);
93 | }
94 | }
95 |
96 | const getNextLayerShape = (initialNodes, layerNumber) => {
97 | let nextLayerShape = 0;
98 | for (let i = 0; i < initialNodes.length; i++) {
99 | if (initialNodes[i].layerInfo.layer_number === layerNumber + 1) {
100 | nextLayerShape = initialNodes[i].layerInfo.output_shape;
101 | break;
102 | }
103 | }
104 | return nextLayerShape;
105 | };
106 |
107 | for (let nodeNum = 0; nodeNum < initialNodes.length; nodeNum++) {
108 | let currentLayerShape = 0;
109 | let nextLayerNumber = 0;
110 | let nextLayerShape = 0;
111 |
112 | if (nodeNum < layerInfo[0].input_shape) {
113 | currentLayerShape = Number(initialNodes[nodeNum].layerInfo.output_shape);
114 | nextLayerShape = currentLayerShape;
115 | nextLayerNumber = initialNodes[nodeNum].layerInfo.layer_number + 1;
116 | } else {
117 | currentLayerShape = Number(initialNodes[nodeNum].layerInfo.output_shape);
118 | nextLayerShape = getNextLayerShape(
119 | initialNodes,
120 | initialNodes[nodeNum].layerInfo.layer_number
121 | );
122 | nextLayerNumber = initialNodes[nodeNum].layerInfo.layer_number + 2;
123 | }
124 |
125 | for (let i = 0; i < nextLayerShape; i++) {
126 | const edge = {
127 | id: `${initialNodes[nodeNum].nodeInfo.id}-layer${nextLayerNumber}-node${
128 | i + 1
129 | }`,
130 | source: initialNodes[nodeNum].nodeInfo.id,
131 | type: 'simplebezier',
132 | target: `layer${nextLayerNumber}-node${i + 1}`,
133 | };
134 |
135 | initialEdges.push(edge);
136 | }
137 | }
138 | console.log('these are our nodes', initialNodes);
139 | console.log('these are our edges', initialEdges);
140 | }
141 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | import HtmlWebpackPlugin from 'html-webpack-plugin';
2 | import path from 'path';
3 | import { fileURLToPath } from 'url';
4 |
5 | const __filename = fileURLToPath(import.meta.url);
6 |
7 | export default {
8 | entry: path.resolve(path.dirname(__filename), 'client', 'index.js'),
9 | output: {
10 | path: path.resolve(path.dirname(__filename), 'build'),
11 | filename: 'bundle.js'
12 | },
13 | mode: "development",
14 | module: {
15 | rules: [
16 | {
17 | test: /\.js$|jsx/,
18 | exclude: /node_modules/,
19 | use: {
20 | loader: 'babel-loader',
21 | options: {
22 | presets: ['@babel/preset-env', '@babel/preset-react']
23 | }
24 | }
25 | },
26 | {
27 | test: /\.s?css$/,
28 | exclude:/(node_modules|bower_components)/,
29 | use: [
30 | { loader: 'style-loader' },
31 | { loader: 'css-loader' }
32 | ]
33 | },
34 | {
35 | test: /\.(jpe?g|png|gif|woff|woff2|eot|ttf|svg)(\?[a-z0-9=.]+)?$/,
36 | use: {
37 | loader: 'url-loader?limit=100000'
38 | }
39 | },
40 | // {
41 | // test: /\.scss/,
42 | // exclude: /node_modules/,
43 | // use: [
44 | // { loader: 'style-loader' },
45 | // { loader: 'sass-loader' }
46 | // ]
47 | // }
48 | ]
49 | },
50 | plugins: [
51 | new HtmlWebpackPlugin({
52 | template: "./client/index.html"
53 | }),
54 | ],
55 | devtool: 'eval-cheap-source-map',
56 | devServer: {
57 | proxy: {
58 | '/export': 'http://localhost:3333'
59 | }
60 | }
61 | };
--------------------------------------------------------------------------------