diff --git a/README.md b/README.md index e2e6cfb..edf4151 100644 --- a/README.md +++ b/README.md @@ -51,53 +51,55 @@ yarn add @untemps/react-vocal import Vocal from '@untemps/react-vocal' const App = () => { - const [result, setResult] = useState('') - - const _onVocalStart = () => { - setResult('') - } - - const _onVocalResult = (result) => { - setResult(result) - } - - return ( -
- - - - -
- ) + const [result, setResult] = useState('') + + const _onVocalStart = () => { + setResult('') + } + + const _onVocalResult = (result) => { + setResult(result) + } + + return ( +
+ + + + +
+ ) } ``` +--- + #### Custom component By default, `Vocal` displays an icon with two states: -- Idle - ![Idle state](assets/icon-idle.png) -- Listening - ![Listening state](assets/icon-listening.png) +- Idle + ![Idle state](assets/icon-idle.png) +- Listening + ![Listening state](assets/icon-listening.png) But you can provide your own component. -- With a simple React element: +- With a simple React element: ```javascript import Vocal from '@untemps/react-vocal' const App = () => { - return ( - - - - ) + return ( + + + + ) } ``` @@ -105,129 +107,187 @@ In this case, a `onClick` handler is automatically attached to the component to Only the first direct descendant of Vocal will receive the `onClick` handler. If you want to use a more complex hierarchy, use the function syntax below. -- With a function that returns a React element: +- With a function that returns a React element: ```javascript import Vocal from '@untemps/react-vocal' -const Play = () =>
- -const Stop = () =>
+const Play = () => ( +
+) + +const Stop = () => ( +
+) const App = () => { - return ( - {(start, stop, isStarted) => ( - - )} - ) + return ( + + {(start, stop, isStarted) => ( + + )} + + ) } ``` The following parameters are passed to the function: -| Arguments | Type | Description | -| ------------- | ----------------- | ------------------------------------------------------------------------------------------------------------ | -| start | func | The function used to start the recognition | -| stop | func | The function used to stop the recognition | -| isStarted | bool | A flag that indicates whether the recognition is started or not | +| Arguments | Type | Description | +| --------- | ---- | --------------------------------------------------------------- | +| start | func | The function used to start the recognition | +| stop | func | The function used to stop the recognition | +| isStarted | bool | A flag that indicates whether the recognition is started or not | + +--- + +#### Commands -#### API +The `Vocal` component accepts a `commands` prop to map special recognition results to callbacks. +That means you can define vocal commands to trigger specific functions. -| Props | Type | Default | Description | -| ------------- | ----------------- | ------- | -------------------------------------------------------------------------------------------------- | -| lang | string | 'en-US' | Language understood by the recognition [BCP 47 language tag](https://tools.ietf.org/html/bcp47) | -| grammars | SpeechGrammarList | null | Grammars understood by the recognition [JSpeech Grammar Format](https://www.w3.org/TR/jsgf/) | -| timeout | number | 3000 | Time in ms to wait before discarding the recognition | -| style | object | null | Styles of the root element if className is not specified | +```javascript +const App = () => { + return ( + setBorderColor('red'), + }}/> + ) +} +``` + +`commands` object is a key/pair model where the `key` is the command to be caught by the recognition and the `value` is the callback triggered when the command is detected. + +`key` is not case sensitive. + +```javascript +const commands = { + submit: () => submitForm(), + 'Change the background color': () => setBackgroundColor('red'), + 'PLAY MUSIC': play +} +``` + +The component utilizes a special hook called `useCommands` to respond to the commands. +The hook performs a fuzzy search to match approximate commands if needed. This allows to fix accidental typos or approximate recognition results. +To do so the hook uses [fuse.js](https://fusejs.io/) which implements an algorithm to find strings that are approximately equal to a given input. The score precision that distinguishes acceptable command-to-callback mapping from negative matching can be customized in the hook instantiantion. + +```javascript +useCommands(commands, threshold) // threshold is the limit not to exceed to be considered a match +``` + +See [fuze.js scoring theory](https://fusejs.io/concepts/scoring-theory.html) for more details. + +> :warning: **The `Vocal` component doesn't expose that score yet.** For now on you have to deal with the default value (*0.4*) + +--- + +#### `Vocal` component API + +| Props | Type | Default | Description | +| ------------- | ----------------- | ------- | ----------------------------------------------------------------------------------------------- | +| commands | object | null | Callbacks to be triggered when specified commands are detected by the recognition | +| lang | string | 'en-US' | Language understood by the recognition [BCP 47 language tag](https://tools.ietf.org/html/bcp47) | +| grammars | SpeechGrammarList | null | Grammars understood by the recognition [JSpeech Grammar Format](https://www.w3.org/TR/jsgf/) | +| timeout | number | 3000 | Time in ms to wait before discarding the recognition | +| style | object | null | Styles of the root element if className is not specified | | className | string | null | Class of the root element | -| onStart | func | null | Handler called when the recognition starts | -| onEnd | func | null | Handler called when the recognition ends | -| onSpeechStart | func | null | Handler called when the speech starts | -| onSpeechEnd | func | null | Handler called when the speech ends | -| onResult | func | null | Handler called when a result is recognized | -| onError | func | null | Handler called when an error occurs | -| onNoMatch | func | null | Handler called when no result can be recognized | +| onStart | func | null | Handler called when the recognition starts | +| onEnd | func | null | Handler called when the recognition ends | +| onSpeechStart | func | null | Handler called when the speech starts | +| onSpeechEnd | func | null | Handler called when the speech ends | +| onResult | func | null | Handler called when a result is recognized | +| onError | func | null | Handler called when an error occurs | +| onNoMatch | func | null | Handler called when no result can be recognized | ### `useVocal` hook #### Basic usage ```javascript -import React, {useState} from 'react' -import {useVocal} from '@untemps/react-vocal' +import React, { useState } from 'react' +import { useVocal } from '@untemps/react-vocal' import Icon from './Icon' const App = () => { - const [isListening, setIsListening] = useState(false) - const [result, setResult] = useState('') - - const [, {start, subscribe}] = useVocal('fr_FR') - - const _onButtonClick = () => { - setIsListening(true) - - subscribe('speechstart', _onVocalStart) - subscribe('result', _onVocalResult) - subscribe('error', _onVocalError) - start() - } - - const _onVocalStart = () => { - setResult('') - } - - const _onVocalResult = (result) => { - setIsListening(false) - - setResult(result) - } - - const _onVocalError = (e) => { - console.error(e) - } - - return ( -
- -
- -
- -
-
- ) + const [isListening, setIsListening] = useState(false) + const [result, setResult] = useState('') + + const [, { start, subscribe }] = useVocal('fr_FR') + + const _onButtonClick = () => { + setIsListening(true) + + subscribe('speechstart', _onVocalStart) + subscribe('result', _onVocalResult) + subscribe('error', _onVocalError) + start() + } + + const _onVocalStart = () => { + setResult('') + } + + const _onVocalResult = (result) => { + setIsListening(false) + + setResult(result) + } + + const _onVocalError = (e) => { + console.error(e) + } + + return ( +
+ +
+ +
+ +
+
+ ) } ``` +--- + #### Signature ``` useVocal(lang, grammars) ``` -| Args | Type | Default | Description | -| ------------- | ----------------- | ------- | ---------------------------------------------------------------------------------------------------- | -| lang | string | 'en-US' | Language understood by the recognition [BCP 47 language tag](https://tools.ietf.org/html/bcp47) | -| grammars | SpeechGrammarList | null | Grammars understood by the recognition [JSpeech Grammar Format](https://www.w3.org/TR/jsgf/) | +| Args | Type | Default | Description | +| -------- | ----------------- | ------- | ----------------------------------------------------------------------------------------------- | +| lang | string | 'en-US' | Language understood by the recognition [BCP 47 language tag](https://tools.ietf.org/html/bcp47) | +| grammars | SpeechGrammarList | null | Grammars understood by the recognition [JSpeech Grammar Format](https://www.w3.org/TR/jsgf/) | + +--- #### Return value @@ -235,47 +295,43 @@ useVocal(lang, grammars) const [ref, { start, stop, abort, subscribe, unsubscribe, clean }] ``` -| Args | Type | Description | -| ------------- | ----------------- | ----------------------------------------------------------- | -| ref | Ref | React ref to the SpeechRecognitionWrapper instance | -| start | func | Function to start the recognition | -| stop | func | Function to stop the recognition | -| abort | func | Function to abort the recognition | -| subscribe | func | Function to subscribe to recognition events | -| unsubscribe | func | Function to unsubscribe to recognition events | -| clean | func | Function to clean subscription to recognition events | +| Args | Type | Description | +| ----------- | ---- | ---------------------------------------------------- | +| ref | Ref | React ref to the SpeechRecognitionWrapper instance | +| start | func | Function to start the recognition | +| stop | func | Function to stop the recognition | +| abort | func | Function to abort the recognition | +| subscribe | func | Function to subscribe to recognition events | +| unsubscribe | func | Function to unsubscribe to recognition events | +| clean | func | Function to clean subscription to recognition events | ### Browser support flag #### Basic usage ```javascript -import Vocal, {isSupported} from '@untemps/react-vocal' +import Vocal, { isSupported } from '@untemps/react-vocal' const App = () => { - return isSupported ? ( - - ) : ( -

Your browser does not support Web Speech API

- ) + return isSupported ? :

Your browser does not support Web Speech API

} ``` ### Events -| Events | Description | -| ------------- | --------------------------------------------------------------------------------------------------- | -| audioend | Fired when the user agent has finished capturing audio for recognition | -| audiostart | Fired when the user agent has started to capture audio for recognition | -| end | Fired when the recognition service has disconnected | -| error | Fired when a recognition error occurs | -| nomatch | Fired when the recognition service returns a final result with no significant recognition | -| result | Fired when the recognition service returns a result | -| soundend | Fired when any sound — recognisable or not — has stopped being detected | -| soundstart | Fired when any sound — recognisable or not — has been detected | -| speechend | Fired when speech recognized by the recognition service has stopped being detected | -| speechstart | Fired when sound recognized by the recognition service as speech has been detected | -| start | fired when the recognition service has begun listening to incoming audio | +| Events | Description | +| ----------- | ----------------------------------------------------------------------------------------- | +| audioend | Fired when the user agent has finished capturing audio for recognition | +| audiostart | Fired when the user agent has started to capture audio for recognition | +| end | Fired when the recognition service has disconnected | +| error | Fired when a recognition error occurs | +| nomatch | Fired when the recognition service returns a final result with no significant recognition | +| result | Fired when the recognition service returns a result | +| soundend | Fired when any sound — recognisable or not — has stopped being detected | +| soundstart | Fired when any sound — recognisable or not — has been detected | +| speechend | Fired when speech recognized by the recognition service has stopped being detected | +| speechstart | Fired when sound recognized by the recognition service as speech has been detected | +| start | fired when the recognition service has begun listening to incoming audio | ### Notes @@ -294,14 +350,14 @@ yarn dev Contributions are warmly welcomed: -- Fork the repository -- Create a feature branch (preferred name convention: `[feature type]_[imperative verb]-[description of the feature]`) -- Develop the feature AND write the tests (or write the tests AND develop the feature) -- Commit your changes - using [Angular Git Commit Guidelines](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines) -- Submit a Pull Request +- Fork the repository +- Create a feature branch (preferred name convention: `[feature type]_[imperative verb]-[description of the feature]`) +- Develop the feature AND write the tests (or write the tests AND develop the feature) +- Commit your changes + using [Angular Git Commit Guidelines](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines) +- Submit a Pull Request ## Roadmap -- Add a connector management to plug external speech-to-text services in -- Support continuous speech +- Add a connector management to plug external speech-to-text services in +- Support continuous speech diff --git a/dev/rollup.config.js b/dev/rollup.config.js index a16385b..c004938 100644 --- a/dev/rollup.config.js +++ b/dev/rollup.config.js @@ -12,7 +12,7 @@ export default { }, plugins: [ replace({ - 'process.env.NODE_ENV': JSON.stringify('production'), + 'process.env.NODE_ENV': JSON.stringify('development'), }), babel({ exclude: 'node_modules/**', diff --git a/dev/src/index.js b/dev/src/index.js index dd1ef97..9f34d81 100644 --- a/dev/src/index.js +++ b/dev/src/index.js @@ -6,6 +6,7 @@ import Vocal from '../../src' const App = () => { const [logs, setLogs] = useState('') + const [borderColor, setBorderColor] = useState() const _log = (value) => setLogs((logs) => `${logs}${logs.length > 0 ? '\n' : ''} ----- ${value}`) @@ -27,8 +28,17 @@ const App = () => { return ( <> - -