├── .cfignore ├── views ├── utils │ ├── zIndices.js │ ├── typography.js │ ├── variables.js │ ├── breakpoints.js │ ├── request.js │ └── colors.js ├── index.jsx ├── ErrorMessage.jsx ├── Output │ ├── OutputTemplate.jsx │ ├── MoreInput.jsx │ ├── Concept.jsx │ ├── Categories.jsx │ ├── Keywords.jsx │ ├── Output.jsx │ ├── Entities.jsx │ ├── Relations.jsx │ ├── Syntax.jsx │ ├── SemanticRoles.jsx │ ├── Sentiment.jsx │ └── Emotion.jsx ├── FloatingCta.jsx ├── Bar.jsx ├── Layout.jsx ├── JsonLink.jsx ├── Table.jsx ├── Input.jsx └── Demo.jsx ├── demo.gif ├── .eslintignore ├── public ├── images │ └── favicon.ico ├── scripts │ ├── polyfills.js │ ├── analytics.js │ └── bundle.jsx └── css │ └── style.css ├── .env.example ├── .gitignore ├── manifest.yml ├── server.js ├── .eslintrc.yml ├── test ├── utils │ └── handleError.js ├── integration │ └── test.mainpage.js └── unit │ └── test.express.js ├── .travis.yml ├── .bluemix └── pipeline.yml ├── config ├── error-handler.js ├── security.js └── express.js ├── casper-runner.js ├── app.js ├── package.json ├── CONTRIBUTING.md ├── README.md ├── LICENSE └── payload.json /.cfignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | uploads 4 | logs 5 | npm-debug.log 6 | .idea 7 | .vscode 8 | -------------------------------------------------------------------------------- /views/utils/zIndices.js: -------------------------------------------------------------------------------- 1 | export const z = { // eslint-disable-line 2 | OVERLAY: '9000', 3 | }; 4 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/natural-language-understanding-nodejs/HEAD/demo.gif -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # this file is compiled 2 | public/js/index.js 3 | public/scripts/analytics.js 4 | coverage 5 | logs 6 | -------------------------------------------------------------------------------- /public/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/natural-language-understanding-nodejs/HEAD/public/images/favicon.ico -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | 2 | NATURAL_LANGUAGE_UNDERSTANDING_IAM_APIKEY= 3 | NATURAL_LANGUAGE_UNDERSTANDING_URL=https://gateway.watsonplatform.net/natural-language-understanding/api 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | uploads/ 4 | .DS_Store 5 | logs/ 6 | .env 7 | *.log 8 | coverage/ 9 | .idea 10 | .vscode 11 | Bluemix_CLI* 12 | IBM_Cloud_CLI_* -------------------------------------------------------------------------------- /manifest.yml: -------------------------------------------------------------------------------- 1 | applications: 2 | - name: natural-language-understanding-demo 3 | path: . 4 | buildpacks: 5 | - nodejs_buildpack 6 | command: npm start 7 | memory: 512M 8 | -------------------------------------------------------------------------------- /public/scripts/polyfills.js: -------------------------------------------------------------------------------- 1 | // todo: serve this file seperately and only load when needed... 2 | 3 | // fetch polyfill for Safari / IE 4 | // automatically sets global 5 | import 'whatwg-fetch'; 6 | -------------------------------------------------------------------------------- /views/utils/typography.js: -------------------------------------------------------------------------------- 1 | export const RHYTHM = '2rem'; 2 | export const weight = { 3 | BOLD: '700', 4 | MEDIUM: '500', 5 | NORMAL: '400', 6 | LIGHT: '300', 7 | }; 8 | export const size = { 9 | SMALL: '0.75rem', 10 | }; 11 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | // load environment properties from a .env file for local development 2 | require('dotenv').config(); 3 | const app = require('./app.js'); 4 | 5 | const port = process.env.PORT || 3000; 6 | 7 | app.listen(port); 8 | console.log('listening at:', port); // eslint-disable-line 9 | -------------------------------------------------------------------------------- /views/utils/variables.js: -------------------------------------------------------------------------------- 1 | import { breakpoint, breakpointsObj as bp } from './breakpoints'; 2 | 3 | export const MAX_CONTENT_WIDTH = '49rem'; 4 | export const targetedLoading = { 5 | textAlign: 'center', 6 | [breakpoint(bp.XX_SMALL)]: { 7 | paddingLeft: '6rem', 8 | marginTop: '2rem', 9 | textAlign: 'left', 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | browser: true 3 | es6: true 4 | node: true 5 | mocha: true 6 | extends: 'eslint:recommended' 7 | globals: 8 | Atomics: readonly 9 | SharedArrayBuffer: readonly 10 | parserOptions: 11 | ecmaFeatures: 12 | jsx: true 13 | ecmaVersion: 2018 14 | sourceType: module 15 | plugins: 16 | - react 17 | rules: {} 18 | -------------------------------------------------------------------------------- /test/utils/handleError.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a try-catch wrapper to ensure that asyncronous tests resolves 3 | */ 4 | function handleError(done, fn) { 5 | try { // boilerplate to be able to get the assert failures 6 | fn(); 7 | done(); 8 | } catch (error) { 9 | done(error); 10 | } 11 | } 12 | 13 | module.exports = handleError; 14 | -------------------------------------------------------------------------------- /public/scripts/analytics.js: -------------------------------------------------------------------------------- 1 | /* eslint no-underscore-dangle: off, no-var: off, vars-on-top: off */ 2 | 3 | function loadAnalytics() { 4 | var idaScript = document.createElement('script'); 5 | idaScript.src = '//www.ibm.com/common/stats/ida_stats.js'; 6 | document.head.appendChild(idaScript); 7 | } 8 | 9 | 10 | window.addEventListener('load', loadAnalytics); 11 | 12 | -------------------------------------------------------------------------------- /public/scripts/bundle.jsx: -------------------------------------------------------------------------------- 1 | import './polyfills'; 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import { StyleSheet } from 'aphrodite'; 5 | import Demo from '../../views/Demo.jsx'; 6 | import { css } from '../../views/index.jsx'; 7 | 8 | 9 | StyleSheet.rehydrate(css.renderedClassNames); 10 | ReactDOM.render(, document.getElementById('root')); 11 | -------------------------------------------------------------------------------- /test/integration/test.mainpage.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable prefer-arrow-callback */ 2 | /* eslint-disable no-undef */ 3 | 4 | casper.test.begin('Natural Language Understanding', 2, function suite(test) { 5 | const baseHost = 'http://localhost:3000'; 6 | 7 | casper.start(baseHost, function start(result) { 8 | casper.test.comment('Starting Testing'); 9 | 10 | test.assert(result.status === 200, 'Front page opens'); 11 | test.assertEquals(this.getTitle(), 'Natural Language Understanding Demo', 'Title is found'); 12 | }); 13 | 14 | casper.run(function run() { 15 | test.done(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /views/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheetServer } from 'aphrodite'; 3 | import ReactDOMServer from 'react-dom/server'; 4 | import HtmlToReact from 'html-to-react'; 5 | import Layout from './Layout.jsx'; 6 | import Demo from './Demo.jsx'; 7 | 8 | // Contains the generated html, as well as the generated css and some 9 | // rehydration data. 10 | const { html, css } = StyleSheetServer.renderStatic(() => ReactDOMServer 11 | .renderToStaticMarkup()); 12 | 13 | export { html, css }; 14 | 15 | export default function Index() { 16 | return ( 17 | 18 | {(new HtmlToReact.Parser(React)).parse(html)} 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: required 3 | node_js: 12 4 | script: 5 | - npm run test 6 | cache: 7 | directories: 8 | - node_modules 9 | env: 10 | global: 11 | - BX_APP=natural-language-understanding-demo 12 | - BX_API=https://api.ng.bluemix.net 13 | - BX_ORGANIZATION=WatsonPlatformServices 14 | - BX_SPACE=demos 15 | before_deploy: npm install -g bx-blue-green 16 | deploy: 17 | - provider: script 18 | skip_cleanup: true 19 | script: bx-blue-green-travis 20 | on: 21 | branch: master 22 | repo: watson-developer-cloud/natural-language-understanding-nodejs 23 | - provider: script 24 | skip_cleanup: true 25 | script: npx semantic-release 26 | -------------------------------------------------------------------------------- /.bluemix/pipeline.yml: -------------------------------------------------------------------------------- 1 | --- 2 | stages: 3 | - name: Build Stage 4 | inputs: 5 | - type: git 6 | branch: master 7 | service: ${REPO} 8 | jobs: 9 | - name: Build 10 | type: builder 11 | artifact_dir: '' 12 | - name: Deploy Stage 13 | inputs: 14 | - type: job 15 | stage: Build Stage 16 | job: Build 17 | triggers: 18 | - type: stage 19 | jobs: 20 | - name: Deploy 21 | type: deployer 22 | target: 23 | region_id: ${CF_REGION_ID} 24 | organization: ${CF_ORGANIZATION} 25 | space: ${CF_SPACE} 26 | application: ${CF_APP} 27 | script: |- 28 | #!/bin/bash 29 | cf create-service natural-language-understanding free my-nlu-service 30 | # Push app 31 | export CF_APP_NAME="$CF_APP" 32 | cf push "${CF_APP_NAME}" 33 | -------------------------------------------------------------------------------- /views/ErrorMessage.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Alert } from 'watson-react-components'; 4 | 5 | const capitalize = string => string.charAt(0).toUpperCase() + string.slice(1); 6 | 7 | export default React.createClass({ 8 | displayName: 'ErrorMessage', 9 | propTypes: { 10 | error: PropTypes.shape({ 11 | error: PropTypes.string, 12 | code: PropTypes.number, 13 | }), 14 | }, 15 | render() { 16 | const { error } = this.props; 17 | return ( 18 |
19 | 20 |

21 | {capitalize(error.error || 'There was a problem processing the request, please try again later.')} 22 |

23 |
24 |
25 | ); 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /views/utils/breakpoints.js: -------------------------------------------------------------------------------- 1 | // Breakpoints 2 | 3 | const breakpointsSource = [ 4 | ['XX_SMALL', '320px'], 5 | ['X_SMALL', '360px'], 6 | ['SMALL', '640px'], 7 | ['MEDIUM', '768px'], 8 | ['LARGE', '1024px'], 9 | ['X_LARGE', '1200px'], 10 | ['XX_LARGE', '1280px'], 11 | ]; 12 | 13 | // Container names 14 | export const breakpointsArr = breakpointsSource.map((e) => e[0]); 15 | // Containers object 16 | export const breakpointsObj = breakpointsSource.reduce( 17 | (prev, next) => { 18 | prev[next[0]] = next[1]; // eslint-disable-line 19 | return prev; 20 | }, {}, 21 | ); 22 | 23 | /** 24 | * function to generate media query 25 | * Example Usage: 26 | * 27 | * breakpoint(breakpoints.XX_SMALL); // returns '@media (min-width: 320px)' 28 | * breakpoint('200px'); // returns '@media (min-width: 200px)' 29 | * 30 | * @param {string} breakpoint css unit value (i.e. 10px, 2rem, etc.) 31 | * @return {string} media query string 32 | */ 33 | export const breakpoint = (breakpt) => `@media (min-width: ${breakpt})`; 34 | -------------------------------------------------------------------------------- /config/error-handler.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 IBM Corp. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /* eslint no-unused-vars: "off" */ 17 | 18 | 19 | module.exports = (app) => { 20 | // catch 404 and forward to error handler 21 | app.use((req, res, next) => { 22 | const err = new Error('Not Found'); 23 | err.code = 404; 24 | err.message = 'Not Found'; 25 | next(err); 26 | }); 27 | 28 | // error handler 29 | app.use((err, req, res, next) => { 30 | const error = { 31 | code: err.code || 500, 32 | error: err.error || err.message, 33 | }; 34 | console.log(error); // eslint-disable-line 35 | res.status(error.code).json(error); 36 | }); 37 | }; 38 | -------------------------------------------------------------------------------- /test/unit/test.express.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 IBM Corp. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | const path = require('path'); 18 | // load default variables for testing 19 | require('dotenv').config({ path: path.join(__dirname, '../../.env.example') }); 20 | 21 | const request = require('supertest'); 22 | const app = require('../../app'); 23 | const handleError = require('../utils/handleError'); 24 | 25 | describe('express', () => { 26 | it('load home page when GET /', (done) => { 27 | handleError(done, () => { 28 | request(app).get('/').expect(200); 29 | }); 30 | }); 31 | 32 | it('404 when page not found', () => request(app).get('/foo/bar').expect(404)); 33 | }); 34 | -------------------------------------------------------------------------------- /views/Output/OutputTemplate.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { StyleSheet, css } from 'aphrodite/no-important'; 4 | import JsonLink from '../JsonLink.jsx'; 5 | 6 | const styles = StyleSheet.create({ 7 | content: { 8 | marginTop: '2rem', 9 | }, 10 | }); 11 | 12 | function OutputTemplate(props) { 13 | return ( 14 |
15 | 22 |
23 | {props.children} 24 |
25 |
26 | ); 27 | } 28 | 29 | OutputTemplate.propTypes = { 30 | showJson: PropTypes.bool, 31 | onExitJson: PropTypes.func, 32 | onShowJson: PropTypes.func, 33 | description: PropTypes.element.isRequired, 34 | data: PropTypes.oneOfType([ 35 | PropTypes.array, 36 | PropTypes.object, 37 | ]), 38 | children: PropTypes.oneOfType([ 39 | PropTypes.arrayOf(PropTypes.node), 40 | PropTypes.node, 41 | ]), 42 | }; 43 | 44 | OutputTemplate.defaultProps = { 45 | showJson: false, 46 | onExitJson: () => {}, 47 | onShowJson: () => {}, 48 | data: {}, 49 | children: {}, 50 | }; 51 | 52 | export default OutputTemplate; 53 | -------------------------------------------------------------------------------- /config/security.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 IBM Corp. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // security.js 18 | const secure = require('express-secure-only'); 19 | const rateLimit = require('express-rate-limit'); 20 | const helmet = require('helmet'); 21 | 22 | module.exports = (app) => { 23 | app.enable('trust proxy'); 24 | 25 | // 1. redirects http to https 26 | app.use(secure()); 27 | 28 | // 2. helmet with defaults 29 | app.use(helmet()); 30 | 31 | // 5. rate limiting. 32 | app.use('/api/', rateLimit({ 33 | windowMs: 30 * 1000, // 30 seconds 34 | delayMs: 0, 35 | max: 3, 36 | message: JSON.stringify({ 37 | error: 'Too many requests, please try again in 30 seconds.', 38 | code: 429, 39 | }), 40 | })); 41 | }; 42 | -------------------------------------------------------------------------------- /views/FloatingCta.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { StyleSheet, css } from 'aphrodite/no-important'; 4 | 5 | const styles = StyleSheet.create({ 6 | container: { 7 | position: 'fixed', 8 | bottom: '4rem', 9 | right: '2rem', 10 | paddingRight: '3em', 11 | paddingLeft: '3em', 12 | paddingTop: '0.2em', 13 | paddingBottom: '0.2em', 14 | backgroundColor: '#9855d4', 15 | borderColor: '#9855d4', 16 | borderRadius: '45px', 17 | zIndex: 9999, 18 | }, 19 | hidden: { 20 | display: 'none', 21 | opacity: 0, 22 | }, 23 | visible: { 24 | display: 'block', 25 | opacity: 1, 26 | }, 27 | ctalabel: { 28 | color: '#FFFFFF', 29 | }, 30 | }); 31 | 32 | const FloatingCta = ({ 33 | link, 34 | label, 35 | isVisible, 36 | }) => { 37 | const combinedVisibleStyles = (isVisible) 38 | ? css(styles.container, styles.visible) 39 | : css(styles.container, styles.hidden); 40 | 41 | return ( 42 | 43 |

{label}

44 |
45 | ); 46 | }; 47 | 48 | FloatingCta.propTypes = { 49 | isVisible: PropTypes.bool.isRequired, 50 | link: PropTypes.string.isRequired, 51 | label: PropTypes.string.isRequired, 52 | }; 53 | 54 | export default FloatingCta; 55 | -------------------------------------------------------------------------------- /views/utils/request.js: -------------------------------------------------------------------------------- 1 | import fetch from 'isomorphic-fetch'; 2 | 3 | const FEATURES = { 4 | features: { 5 | concepts: {}, 6 | entities: {}, 7 | keywords: {}, 8 | categories: {}, 9 | emotion: {}, 10 | sentiment: {}, 11 | semantic_roles: {}, 12 | syntax: { 13 | tokens: { 14 | lemma: true, 15 | part_of_speech: true, 16 | }, 17 | sentences: true, 18 | }, 19 | }, 20 | }; 21 | 22 | 23 | const parseJSON = (response) => { // eslint-disable-line 24 | return response.json(); 25 | }; 26 | 27 | const handleErrors = (response) => { 28 | if (response.error) { 29 | throw response; 30 | } 31 | return response; 32 | }; 33 | 34 | /** 35 | * Calls the NLU /analyze endpoint 36 | * 37 | * @param {Object} params The parameters 38 | * @return {Promise} The request promise 39 | */ 40 | export const analyze = (params) => fetch('/api/analyze', { 41 | method: 'POST', 42 | headers: { 'content-type': 'application/json' }, 43 | body: JSON.stringify(params), 44 | }) 45 | .then(parseJSON) 46 | .then(handleErrors); 47 | 48 | 49 | /** 50 | * Extend the `params` parameters with all the text 51 | * features before calling `analyze`. 52 | * 53 | * @param {Object} params The parameters 54 | * @return {Promise} The request promise 55 | */ 56 | export const analyzeWithAllFeatures = (params) => { 57 | const query = { ...FEATURES, ...params }; 58 | return analyze(query); 59 | }; 60 | -------------------------------------------------------------------------------- /views/Output/MoreInput.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Icon } from 'watson-react-components'; 4 | import { StyleSheet, css } from 'aphrodite/no-important'; 5 | 6 | const styles = StyleSheet.create({ 7 | container: { 8 | width: 'calc(100% - 2rem)', 9 | maxWidth: '16.5rem', 10 | position: 'relative', 11 | marginTop: '1rem', 12 | marginBottom: '1rem', 13 | }, 14 | button: { 15 | position: 'absolute', 16 | top: 'calc(50% - 0.82rem)', 17 | left: '100%', 18 | marginTop: '0rem', 19 | marginLeft: '1rem', 20 | border: 'none', 21 | padding: '0rem', 22 | }, 23 | }); 24 | 25 | let value = ''; 26 | 27 | function MoreInput(props) { 28 | return ( 29 |
30 | { 35 | value = e.target.value; 36 | if (e.keyCode === 13) { 37 | props.onSubmit.call(this, e); 38 | } 39 | }} 40 | /> 41 | 48 |
49 | ); 50 | } 51 | 52 | MoreInput.propTypes = { 53 | onSubmit: PropTypes.func, 54 | }; 55 | 56 | MoreInput.defaultProps = { 57 | onSubmit: () => {}, 58 | }; 59 | 60 | export default MoreInput; 61 | -------------------------------------------------------------------------------- /casper-runner.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 IBM Corp. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | require('dotenv').config({ silent: true }); 17 | 18 | if (!process.env.NATURAL_LANGUAGE_UNDERSTANDING_IAM_APIKEY) { 19 | console.log( 20 | 'Skipping integration tests because NATURAL_LANGUAGE_UNDERSTANDING_IAM_APIKEY is null', 21 | ); // eslint-disable-line 22 | process.exit(0); 23 | } 24 | 25 | const { spawn } = require('child_process'); 26 | 27 | const app = require('./app'); 28 | 29 | const port = 3000; 30 | 31 | const server = app.listen(port, () => { 32 | console.log('Server running on port: %d', port); // eslint-disable-line 33 | 34 | function kill(code) { 35 | server.close(() => { 36 | process.exit(code); 37 | }); 38 | } 39 | 40 | function runTests() { 41 | const casper = spawn('npm', ['run', 'test-integration']); 42 | casper.stdout.pipe(process.stdout); 43 | 44 | casper.on('error', (error) => { 45 | console.log(`ERROR: ${error}`); // eslint-disable-line 46 | server.close(() => { 47 | process.exit(1); 48 | }); 49 | }); 50 | 51 | casper.on('close', kill); 52 | } 53 | 54 | runTests(); 55 | }); 56 | -------------------------------------------------------------------------------- /config/express.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 IBM Corp. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Module dependencies 18 | const express = require('express'); 19 | const expressBrowserify = require('express-browserify'); 20 | const path = require('path'); 21 | 22 | 23 | module.exports = (app) => { 24 | app.enable('trust proxy'); 25 | app.set('view engine', 'jsx'); 26 | app.engine('jsx', require('express-react-views').createEngine()); 27 | 28 | 29 | // Only loaded when running in Bluemix 30 | if (process.env.VCAP_APPLICATION) { 31 | require('./security')(app); 32 | } 33 | 34 | // automatically bundle the front-end js on the fly 35 | // note: this should come before the express.static since bundle.js is in the public folder 36 | const isDev = (app.get('env') === 'development'); 37 | const browserifyier = expressBrowserify('./public/scripts/bundle.jsx', { 38 | watch: isDev, 39 | debug: isDev, 40 | extension: ['jsx'], 41 | transform: ['babelify'], 42 | }); 43 | if (!isDev) { 44 | browserifyier.browserify.transform('uglifyify', { global: true }); 45 | } 46 | app.get('/scripts/bundle.js', browserifyier); 47 | 48 | // Configure Express 49 | app.use(express.static(path.join(__dirname, '..', 'public'))); 50 | app.use(express.static(path.join(__dirname, '..', 'node_modules/watson-react-components/dist/'))); 51 | }; 52 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 IBM Corp. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | const express = require('express'); 18 | const NaturalLanguageUnderstandingV1 = require('ibm-watson/natural-language-understanding/v1.js'); 19 | const { IamAuthenticator } = require('ibm-watson/auth'); 20 | 21 | const app = express(); 22 | // Create the service wrapper 23 | const nlu = new NaturalLanguageUnderstandingV1({ 24 | version: '2021-10-15', 25 | authenticator: new IamAuthenticator({ 26 | apikey: process.env.NATURAL_LANGUAGE_UNDERSTANDING_IAM_APIKEY || 'type-key-here', 27 | }), 28 | url: process.env.NATURAL_LANGUAGE_UNDERSTANDING_URL, 29 | }); 30 | 31 | // setup body-parser 32 | const bodyParser = require('body-parser'); 33 | 34 | app.use(bodyParser.json()); 35 | 36 | // Bootstrap application settings 37 | require('./config/express')(app); 38 | 39 | app.get('/', (req, res) => { 40 | res.render('index'); 41 | }); 42 | 43 | app.post('/api/analyze', (req, res, next) => { 44 | if (process.env.SHOW_DUMMY_DATA) { 45 | res.json(require('./payload.json')); 46 | } else { 47 | nlu.analyze(req.body, (err, results) => { 48 | if (err) { 49 | return next(err); 50 | } 51 | return res.json({ query: req.body.query, results: results.result }); 52 | }); 53 | } 54 | }); 55 | 56 | // error-handler settings 57 | require('./config/error-handler')(app); 58 | 59 | module.exports = app; 60 | -------------------------------------------------------------------------------- /views/Bar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import numeral from 'numeral'; 4 | import map from 'map-range'; 5 | import { parser } from 'css-math'; 6 | import { StyleSheet, css } from 'aphrodite/no-important'; 7 | import { colors } from './utils/colors'; 8 | 9 | const barColor = colors.GRAY; 10 | const scoreLabelWidth = '1.5rem'; 11 | const scorePadding = '0.5rem'; 12 | 13 | const styles = StyleSheet.create({ 14 | container: { 15 | display: 'flex', 16 | alignItems: 'center', 17 | width: '100%', 18 | maxWidth: '6rem', 19 | }, 20 | fullBar: { 21 | height: '0.5rem', 22 | border: `1px solid ${barColor}`, 23 | position: 'relative', 24 | width: `calc(100% - ${parser(`${scoreLabelWidth} + ${scorePadding}`)})`, 25 | marginRight: scorePadding, 26 | }, 27 | fullBar_barOnly: { 28 | width: '100%', 29 | }, 30 | bar: { 31 | backgroundColor: barColor, 32 | height: '100%', 33 | position: 'absolute', 34 | top: '0rem', 35 | left: '0rem', 36 | }, 37 | score: { 38 | width: scoreLabelWidth, 39 | textAlign: 'right', 40 | marginTop: '0rem', 41 | verticalAlign: 'right', 42 | fontSize: '0.8rem', 43 | }, 44 | }); 45 | 46 | function Bar(props) { 47 | const { 48 | rangeEnd, rangeStart, score, withScore, 49 | } = props; 50 | const mapped = map(x => x, rangeStart, rangeEnd, 0, 1); 51 | return (withScore 52 | ? ( 53 |
54 |
55 |
56 |
57 |
58 | {numeral(score).format('0.00')} 59 |
60 |
61 | ) 62 | : ( 63 |
64 |
65 |
66 | ) 67 | ); 68 | } 69 | 70 | Bar.propTypes = { 71 | score: PropTypes.number, // percentage number from 0 - 100 72 | withScore: PropTypes.bool, // show score or not 73 | rangeStart: PropTypes.number, 74 | rangeEnd: PropTypes.number, 75 | }; 76 | 77 | Bar.defaultProps = { 78 | score: 0, 79 | withScore: true, 80 | rangeStart: 0, 81 | rangeEnd: 1, 82 | }; 83 | 84 | export default Bar; 85 | -------------------------------------------------------------------------------- /views/Output/Concept.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import OutputTemplate from './OutputTemplate.jsx'; 4 | import { breakpoint, breakpointsObj as bp } from '../utils/breakpoints'; 5 | import { colors } from '../utils/colors'; 6 | import { weight } from '../utils/typography'; 7 | import Table from '../Table.jsx'; 8 | import Bar from '../Bar.jsx'; 9 | 10 | const tableTheme = { 11 | row: { 12 | maxWidth: '300px', 13 | borderBottom: `1px solid ${colors.WARM_GRAY}`, 14 | paddingBottom: '1rem', 15 | marginBottom: '1rem', 16 | }, 17 | row_header: { 18 | [breakpoint(bp.SMALL)]: { 19 | borderBottom: 'none', 20 | }, 21 | }, 22 | cell: { 23 | ':first-child': { 24 | fontWeight: weight.MEDIUM, 25 | color: colors.PRIMARY_LIGHT, 26 | }, 27 | [breakpoint(bp.X_SMALL)]: { 28 | width: '7rem', 29 | ':first-child': { 30 | width: 'calc(100% - 7rem)', 31 | }, 32 | }, 33 | }, 34 | cell_header: { 35 | fontWeight: weight.MEDIUM, 36 | color: colors.COOL_GRAY, 37 | ':first-child': { 38 | fontWeight: weight.MEDIUM, 39 | color: colors.COOL_GRAY, 40 | }, 41 | }, 42 | }; 43 | 44 | export default React.createClass({ 45 | displayName: 'Concept', 46 | 47 | propTypes: { 48 | data: PropTypes.arrayOf(PropTypes.shape({ 49 | text: PropTypes.string, 50 | relevance: PropTypes.number, 51 | })), 52 | language: PropTypes.string, 53 | }, 54 | 55 | getInitialState() { 56 | return { 57 | showJson: false, 58 | }; 59 | }, 60 | 61 | toggleJson() { 62 | this.setState({ showJson: !this.state.showJson }); 63 | }, 64 | 65 | render() { 66 | return ( 67 |
68 | Identifies general concepts that may not be directly referenced in the text.

} 70 | data={{ concepts: this.props.data }} 71 | showJson={this.state.showJson} 72 | onExitJson={this.toggleJson} 73 | onShowJson={this.toggleJson} 74 | > 75 | {this.props.data && this.props.data.length > 0 ? ( 76 | 80 | ({ Concept: item.text, Score: })) 81 | } 82 | /> 83 | ) : ( 84 |

{`No Concept results returned for ${this.props.language} input.`}

85 | )} 86 | 87 | 88 | ); 89 | }, 90 | }); 91 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ibm-watson/natural-language-understanding-nodejs", 3 | "version": "0.0.2", 4 | "description": "A sample Node.js app for IBM Cloud that use the IBM Watson Natural Language Understanding service", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/watson-developer-cloud/natural-language-understanding-nodejs.git" 8 | }, 9 | "engines": { 10 | "node": ">=12" 11 | }, 12 | "author": "IBM Corp.", 13 | "babel": { 14 | "presets": [ 15 | [ 16 | "@babel/preset-env", 17 | { 18 | "useBuiltIns": "entry", 19 | "corejs": "3.0" 20 | } 21 | ], 22 | "@babel/preset-react" 23 | ] 24 | }, 25 | "license": "Apache-2.0", 26 | "bugs": { 27 | "url": "https://github.com/watson-developer-cloud/natural-language-understanding-nodejs/issues" 28 | }, 29 | "scripts": { 30 | "start": "node server.js", 31 | "test-integration": "casperjs test ./test/integration/test.*.js", 32 | "test-integration-runner": "NODE_ENV=test node casper-runner.js", 33 | "test": "npm run lint && npm run test-unit && npm run test-integration-runner", 34 | "test-unit": "mocha test/unit --exit", 35 | "lint": "npx eslint .", 36 | "autofix": "npx eslint --fix .", 37 | "codecov": "npm run test && (codecov || true)" 38 | }, 39 | "dependencies": { 40 | "@babel/core": "^7.7.0", 41 | "@babel/preset-env": "^7.7.1", 42 | "@babel/preset-react": "^7.7.0", 43 | "@babel/register": "^7.7.0", 44 | "aphrodite": "^1.2.5", 45 | "babel-eslint": "^10.0.3", 46 | "babelify": "^10.0.0", 47 | "body-parser": "^1.19.0", 48 | "core-js": "^3.4.0", 49 | "css-math": "^0.4.0", 50 | "deep-assign": "^2.0.0", 51 | "dotenv": "^8.2.0", 52 | "es6-promise": "^4.2.8", 53 | "express": "^4.17.1", 54 | "express-browserify": "^1.0.3", 55 | "express-rate-limit": "^5.0.0", 56 | "express-react-views": "^0.11.0", 57 | "express-secure-only": "^0.2.1", 58 | "helmet": "^3.21.2", 59 | "html-to-react": "^1.4.2", 60 | "ibm-watson": "^5.1.0", 61 | "isomorphic-fetch": "^2.2.1", 62 | "language-list": "0.0.3", 63 | "map-range": "^0.1.2", 64 | "numeral": "^2.0.6", 65 | "prop-types": "^15.7.2", 66 | "raf": "^3.4.1", 67 | "react": "^15.6.2", 68 | "react-dom": "^15.6.2", 69 | "react-waypoint": "^8.1.0", 70 | "scroll-to-element": "^2.0.3", 71 | "tween": "^0.9.0", 72 | "uglifyify": "^5.0.2", 73 | "valid-url": "^1.0.9", 74 | "watson-react-components": "^0.6.19", 75 | "watson-ui-components": "^0.6.2", 76 | "whatwg-fetch": "^2.0.4" 77 | }, 78 | "devDependencies": { 79 | "casperjs": "^1.1.4", 80 | "codecov": "^3.6.1", 81 | "eslint": "^6.6.0", 82 | "eslint-config-airbnb": "^18.0.1", 83 | "eslint-plugin-import": "^2.18.2", 84 | "eslint-plugin-jsx-a11y": "^6.2.3", 85 | "eslint-plugin-react": "^7.16.0", 86 | "jest": "^24.9.0", 87 | "mocha": "^6.2.2", 88 | "nock": "^11.7.0", 89 | "phantomjs-prebuilt": "^2.1.16", 90 | "supertest": "^4.0.2" 91 | }, 92 | "publishConfig": { 93 | "registry": "https://registry.npmjs.org/", 94 | "access": "public" 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Questions 2 | 3 | If you are having problems using the APIs or have a question about the IBM 4 | Watson Services, please ask a question on 5 | [dW Answers](https://developer.ibm.com/answers/questions/ask/?topics=watson) 6 | or [Stack Overflow](http://stackoverflow.com/questions/ask?tags=ibm-watson). 7 | 8 | # Code 9 | 10 | - Our style guide is based on [Google's](https://google.github.io/styleguide/jsguide.html), most of it is automaticaly enforced (and can be automatically applied with `npm run autofix`) 11 | - Commits should follow the [Angular commit message guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-commit-message-guidelines). This is because our release tool uses this format for determining release versions and generating changelogs. To make this easier, we recommend using the [Commitizen CLI](https://github.com/commitizen/cz-cli) with the `cz-conventional-changelog` adapter. 12 | 13 | # Issues 14 | 15 | If you encounter an issue with the Node.js library, you are welcome to submit 16 | a [bug report](https://github.com/watson-developer-cloud/natural-language-understanding-nodejs/issues). 17 | Before that, please search for similar issues. It's possible somebody has 18 | already encountered this issue. 19 | 20 | # Pull Requests 21 | 22 | If you want to contribute to the repository, follow these steps: 23 | 24 | 1. Fork the repo. 25 | 2. Develop and test your code changes: `npm install -d && npm test`. 26 | 3. Travis-CI will run the tests for all services once your changes are merged. 27 | 4. Add a test for your changes. Only refactoring and documentation changes require no new tests. 28 | 5. Make the test pass. 29 | 6. Commit your changes. 30 | 7. Push to your fork and submit a pull request. 31 | 32 | # Developer's Certificate of Origin 1.1 33 | 34 | By making a contribution to this project, I certify that: 35 | 36 | (a) The contribution was created in whole or in part by me and I 37 | have the right to submit it under the open source license 38 | indicated in the file; or 39 | 40 | (b) The contribution is based upon previous work that, to the best 41 | of my knowledge, is covered under an appropriate open source 42 | license and I have the right under that license to submit that 43 | work with modifications, whether created in whole or in part 44 | by me, under the same open source license (unless I am 45 | permitted to submit under a different license), as indicated 46 | in the file; or 47 | 48 | (c) The contribution was provided directly to me by some other 49 | person who certified (a), (b) or (c) and I have not modified 50 | it. 51 | 52 | (d) I understand and agree that this project and the contribution 53 | are public and that a record of the contribution (including all 54 | personal information I submit with it, including my sign-off) is 55 | maintained indefinitely and may be redistributed consistent with 56 | this project or the open source license(s) involved. 57 | 58 | ## Tests 59 | 60 | Ideally, we'd like to see both unit and innervation tests on each method. 61 | (Unit tests do not actually connect to the Watson service, integration tests do.) 62 | 63 | Out of the box, `npm test` runs linting and unit tests, but skips the integration tests, 64 | because they require credentials. 65 | -------------------------------------------------------------------------------- /views/Output/Categories.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import OutputTemplate from './OutputTemplate.jsx'; 4 | import { breakpoint, breakpointsObj as bp } from '../utils/breakpoints'; 5 | import { colors } from '../utils/colors'; 6 | import { weight } from '../utils/typography'; 7 | import Table from '../Table.jsx'; 8 | import Bar from '../Bar.jsx'; 9 | 10 | const tableTheme = { 11 | row_two: { 12 | marginBottom: '1rem', 13 | // maxWidth: '300px', 14 | [breakpoint(bp.SMALL)]: { 15 | borderBottom: `1px solid ${colors.WARM_GRAY}`, 16 | paddingBottom: '1rem', 17 | marginBottom: '1rem', 18 | }, 19 | }, 20 | row_header_two: { 21 | [breakpoint(bp.SMALL)]: { 22 | borderBottom: 'none', 23 | paddingBottom: '0rem', 24 | }, 25 | }, 26 | cell_header_two: { 27 | fontWeight: weight.MEDIUM, 28 | color: colors.COOL_GRAY, 29 | ':first-child': { 30 | fontWeight: weight.MEDIUM, 31 | color: colors.COOL_GRAY, 32 | }, 33 | }, 34 | cell_two: { 35 | marginBottom: '0.5rem', 36 | ':first-child': { 37 | color: colors.PRIMARY_LIGHT, 38 | fontWeight: weight.BOLD, 39 | }, 40 | [breakpoint(bp.SMALL)]: { 41 | marginBottom: '0.5rem', 42 | width: 'calc(100% - 7rem)', 43 | ':nth-of-type(2)': { 44 | width: '7rem', 45 | }, 46 | }, 47 | }, 48 | }; 49 | 50 | 51 | export default React.createClass({ 52 | displayName: 'Categories', 53 | 54 | propTypes: { 55 | data: PropTypes.arrayOf(PropTypes.shape({ 56 | label: PropTypes.string, 57 | score: PropTypes.number, 58 | })), 59 | language: PropTypes.string, 60 | }, 61 | 62 | getInitialState() { 63 | return { 64 | showJson: false, 65 | }; 66 | }, 67 | 68 | toggleJson() { 69 | this.setState(prevState => ({ showJson: !prevState.showJson })); 70 | }, 71 | 72 | render() { 73 | const { data, language } = this.props; 74 | const { showJson } = this.state; 75 | return ( 76 |
77 | Classify content into a hierarchy that's five levels deep with a score.

} 79 | data={{ categories: data }} 80 | showJson={showJson} 81 | onExitJson={this.toggleJson} 82 | onShowJson={this.toggleJson} 83 | > 84 | {data && data.length > 0 ? ( 85 |
{ 90 | acc.push({ Hierarchy: item.label.split('/').join(' / '), Score: }); 91 | return acc; 92 | }, [])} 93 | /> 94 | ) : ( 95 |

96 | {`No Categories results returned for ${language} input.`} 97 |

98 | )} 99 | 100 | 101 | ); 102 | }, 103 | }); 104 | -------------------------------------------------------------------------------- /views/Output/Keywords.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Waypoint from 'react-waypoint'; 4 | import OutputTemplate from './OutputTemplate.jsx'; 5 | import { breakpoint, breakpointsObj as bp } from '../utils/breakpoints'; 6 | import { colors } from '../utils/colors'; 7 | import { weight } from '../utils/typography'; 8 | import Table from '../Table.jsx'; 9 | import Bar from '../Bar.jsx'; 10 | 11 | const tableTheme = { 12 | row: { 13 | maxWidth: '360px', 14 | borderBottom: `1px solid ${colors.WARM_GRAY}`, 15 | paddingBottom: '1rem', 16 | marginBottom: '1rem', 17 | }, 18 | row_header: { 19 | [breakpoint(bp.SMALL)]: { 20 | borderBottom: 'none', 21 | }, 22 | }, 23 | cell: { 24 | ':first-child': { 25 | fontWeight: weight.MEDIUM, 26 | color: colors.PRIMARY_LIGHT, 27 | }, 28 | [breakpoint(bp.X_SMALL)]: { 29 | width: '7rem', 30 | ':first-child': { 31 | width: 'calc(100% - 7rem)', 32 | }, 33 | }, 34 | }, 35 | cell_header: { 36 | fontWeight: weight.MEDIUM, 37 | color: colors.COOL_GRAY, 38 | ':first-child': { 39 | fontWeight: weight.MEDIUM, 40 | color: colors.COOL_GRAY, 41 | }, 42 | }, 43 | }; 44 | 45 | export default React.createClass({ 46 | displayName: 'Keywords', 47 | 48 | propTypes: { 49 | data: PropTypes.arrayOf(PropTypes.shape({ 50 | text: PropTypes.string, 51 | relevance: PropTypes.number, 52 | })), 53 | language: PropTypes.string, 54 | }, 55 | 56 | getInitialState() { 57 | return { 58 | showJson: false, 59 | visibleItems: 10, 60 | }; 61 | }, 62 | 63 | toggleJson() { 64 | this.setState({ showJson: !this.state.showJson }); 65 | }, 66 | 67 | onWaypoint() { 68 | if (this.state.visibleItems < this.props.data.length) { 69 | setTimeout(() => { 70 | this.setState({ 71 | visibleItems: this.state.visibleItems + 10, 72 | }); 73 | }, 150); 74 | } 75 | }, 76 | 77 | render() { 78 | return ( 79 |
80 | Determine important keywords ranked by relevance.

} 82 | data={{ keywords: this.props.data }} 83 | showJson={this.state.showJson} 84 | onExitJson={this.toggleJson} 85 | onShowJson={this.toggleJson} 86 | > 87 | {this.props.data && this.props.data.length > 0 ? ( 88 |
89 |
({ 93 | Text: item.text, 94 | Relevance: , 95 | })).filter((val, i) => i <= this.state.visibleItems)} 96 | /> 97 | 98 | 99 | ) : ( 100 |

{`No Keywords results returned for ${this.props.language} input.`}

101 | )} 102 | 103 | 104 | ); 105 | }, 106 | }); 107 | -------------------------------------------------------------------------------- /views/Layout.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Header, Jumbotron } from 'watson-react-components'; 4 | 5 | const demoName = 'Natural Language Understanding'; 6 | const DESCRIPTION = 'Natural Language Understanding is a collection of APIs that offer text analysis through natural language processing. This set of APIs can analyze text to help you understand its concepts, entities, keywords, sentiment, and more. Additionally, you can create a custom model for some APIs to get specific results that are tailored to your domain. This system is for demonstration purposes only and is not intended to process Personal Data. No Personal Data is to be entered into this system as it may not have the necessary controls in place to meet the requirements of the General Data Protection Regulation (EU) 2016/679.'; 7 | 8 | export default function Layout(props) { 9 | const { css, children } = props; 10 | return ( 11 | 12 | 13 | 14 | {`${demoName} Demo`} 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 |