@untemps/react-vocal 2.0.0-beta.3 → 2.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +28 -15
- package/dev/src/index.jsx +39 -26
- package/dist/index.es.js +172 -1297
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +2 -2
- package/dist/index.umd.js.map +1 -1
- package/package.json +9 -3
- package/src/components/Vocal.jsx +25 -9
- package/src/components/__tests__/Vocal.test.jsx +144 -9
- package/src/hooks/__tests__/useCommands.test.js +53 -2
- package/src/hooks/__tests__/useVocal.test.js +6 -1
- package/src/hooks/useCommands.js +63 -9
- package/src/hooks/useVocal.js +3 -3
- package/vite.config.js +2 -1
- package/vitest.setup.js +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
# [2.0.0-beta.5](https://github.com/untemps/react-vocal/compare/v2.0.0-beta.4...v2.0.0-beta.5) (2026-05-11)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* Make fuse.js an optional peer dependency ([#117](https://github.com/untemps/react-vocal/issues/117)) ([a1c2a33](https://github.com/untemps/react-vocal/commit/a1c2a337f8d4ba4729c81f0a9b8664bcf914755f))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### BREAKING CHANGES
|
|
10
|
+
|
|
11
|
+
* fuse.js must now be installed separately to enable fuzzy matching for phrase commands.
|
|
12
|
+
|
|
13
|
+
# [2.0.0-beta.4](https://github.com/untemps/react-vocal/compare/v2.0.0-beta.3...v2.0.0-beta.4) (2026-05-10)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
* Per-segment command matching, maxAlternatives, and exact lookup ([39b6370](https://github.com/untemps/react-vocal/commit/39b63701b2a15c058b5ae510be8ad4b4437d6763))
|
|
19
|
+
|
|
1
20
|
# [2.0.0-beta.3](https://github.com/untemps/react-vocal/compare/v2.0.0-beta.2...v2.0.0-beta.3) (2026-05-07)
|
|
2
21
|
|
|
3
22
|
|
package/README.md
CHANGED
|
@@ -46,6 +46,14 @@ Although the lack of `SpeechGrammar` and `SpeechGrammarList` is handled by the u
|
|
|
46
46
|
yarn add @untemps/react-vocal
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
Fuzzy matching for phrase commands requires [fuse.js](https://fusejs.io/) as an optional peer dependency:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
yarn add fuse.js
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Without fuse.js, phrase commands fall back to case-insensitive exact matching. Single-word commands always use exact matching and never require fuse.js.
|
|
56
|
+
|
|
49
57
|
## Usage
|
|
50
58
|
|
|
51
59
|
### `Vocal` component
|
|
@@ -192,15 +200,17 @@ const commands = {
|
|
|
192
200
|
|
|
193
201
|
The component utilizes a special hook called `useCommands` to respond to the commands.
|
|
194
202
|
The hook performs a fuzzy search to match approximate commands if needed. This allows to fix accidental typos or approximate recognition results.
|
|
195
|
-
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
|
|
203
|
+
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 instantiation.
|
|
196
204
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
205
|
+
fuse.js is an optional peer dependency — install it separately to enable fuzzy matching (see [Installation](#installation)). Without it, phrase commands fall back to case-insensitive exact matching.
|
|
206
|
+
|
|
207
|
+
**Single-word command keys** (e.g. `rouge`, `submit`) use exact case-insensitive lookup. When the recognition returns a multi-word transcript, each word is tried individually so a command fires even when embedded in a phrase (e.g. _"je veux du rouge"_ triggers `rouge`).
|
|
208
|
+
|
|
209
|
+
**Phrase command keys** (e.g. `'Change the background color'`) use [fuse.js](https://fusejs.io/) fuzzy matching. The `precision` prop controls the Fuse.js score threshold (default `0.4` — lower is stricter).
|
|
200
210
|
|
|
201
|
-
|
|
211
|
+
**Homophone tolerance** is achieved via `maxAlternatives`: by setting it to 3–5, the speech engine returns several transcription candidates per segment. The correct word (e.g. _vert_) may appear as a secondary alternative when the primary is a homophone (e.g. _verre_), and will still trigger the command.
|
|
202
212
|
|
|
203
|
-
|
|
213
|
+
**At most one command fires per utterance.** Alternatives and segments are scanned in order and matching stops at the first hit, so a single recognition event can trigger at most one command callback.
|
|
204
214
|
|
|
205
215
|
---
|
|
206
216
|
|
|
@@ -208,10 +218,12 @@ See [fuze.js scoring theory](https://fusejs.io/concepts/scoring-theory.html) for
|
|
|
208
218
|
|
|
209
219
|
| Props | Type | Default | Description |
|
|
210
220
|
| ------------- | ----------------- | -------------------- | ----------------------------------------------------------------------------------------------- |
|
|
211
|
-
| commands
|
|
212
|
-
| lang
|
|
213
|
-
| grammars
|
|
214
|
-
| timeout
|
|
221
|
+
| commands | object | null | Callbacks to be triggered when specified commands are detected by the recognition |
|
|
222
|
+
| lang | string | 'en-US' | Language understood by the recognition [BCP 47 language tag](https://tools.ietf.org/html/bcp47) |
|
|
223
|
+
| grammars | SpeechGrammarList | null | Grammars understood by the recognition [JSpeech Grammar Format](https://www.w3.org/TR/jsgf/) |
|
|
224
|
+
| timeout | number | 3000 | Time in ms to wait before discarding the recognition |
|
|
225
|
+
| precision | number | 0.4 | Fuse.js score threshold for **phrase** command keys only (lower = stricter). Single-word commands always use exact lookup. |
|
|
226
|
+
| maxAlternatives | number | 1 | Maximum number of recognition alternatives per segment. Setting this to 3–5 lets the engine surface the correct word as a secondary transcript, which is useful for handling homophones (e.g. _vert_ / _verre_ in French). |
|
|
215
227
|
| style | object | null | Styles of the root element if className is not specified |
|
|
216
228
|
| className | string | null | Class of the root element |
|
|
217
229
|
| ariaLabel | string | 'start recognition' | Accessible label for the default button |
|
|
@@ -286,13 +298,14 @@ const App = () => {
|
|
|
286
298
|
#### Signature
|
|
287
299
|
|
|
288
300
|
```
|
|
289
|
-
useVocal(lang, grammars)
|
|
301
|
+
useVocal(lang, grammars, maxAlternatives)
|
|
290
302
|
```
|
|
291
303
|
|
|
292
|
-
| Args
|
|
293
|
-
|
|
|
294
|
-
| lang
|
|
295
|
-
| grammars
|
|
304
|
+
| Args | Type | Default | Description |
|
|
305
|
+
| --------------- | ----------------- | ------- | ----------------------------------------------------------------------------------------------- |
|
|
306
|
+
| lang | string | 'en-US' | Language understood by the recognition [BCP 47 language tag](https://tools.ietf.org/html/bcp47) |
|
|
307
|
+
| grammars | SpeechGrammarList | null | Grammars understood by the recognition [JSpeech Grammar Format](https://www.w3.org/TR/jsgf/) |
|
|
308
|
+
| maxAlternatives | number | 1 | Maximum number of recognition alternatives per segment |
|
|
296
309
|
|
|
297
310
|
---
|
|
298
311
|
|
package/dev/src/index.jsx
CHANGED
|
@@ -1,43 +1,56 @@
|
|
|
1
|
-
import React, { useState } from 'react'
|
|
1
|
+
import React, { useMemo, useState } from 'react'
|
|
2
2
|
import { createRoot } from 'react-dom/client'
|
|
3
3
|
|
|
4
4
|
import Vocal from '../../src'
|
|
5
5
|
|
|
6
|
+
const COMMANDS = {
|
|
7
|
+
rouge: 'red',
|
|
8
|
+
bleu: 'blue',
|
|
9
|
+
vert: 'green',
|
|
10
|
+
jaune: 'yellow',
|
|
11
|
+
}
|
|
12
|
+
|
|
6
13
|
const App = () => {
|
|
7
14
|
const [logs, setLogs] = useState('')
|
|
8
15
|
const [borderColor, setBorderColor] = useState()
|
|
9
16
|
|
|
10
|
-
const _log = (value) => setLogs((
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
const _log = (value) => setLogs((prev) => `${prev}${prev.length > 0 ? '\n' : ''} ----- ${value}`)
|
|
18
|
+
|
|
19
|
+
// Memoized to avoid recreating the commands object (and re-indexing useCommands) on every render
|
|
20
|
+
const commands = useMemo(
|
|
21
|
+
() =>
|
|
22
|
+
Object.fromEntries(
|
|
23
|
+
Object.entries(COMMANDS).map(([key, color]) => [
|
|
24
|
+
key,
|
|
25
|
+
(input) => {
|
|
26
|
+
_log(`command matched: "${input}" → ${color}`)
|
|
27
|
+
setBorderColor(color)
|
|
28
|
+
},
|
|
29
|
+
])
|
|
30
|
+
),
|
|
31
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
32
|
+
[]
|
|
33
|
+
)
|
|
27
34
|
|
|
28
35
|
return (
|
|
29
36
|
<>
|
|
30
37
|
<Vocal
|
|
31
38
|
lang="fr"
|
|
32
|
-
commands={
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
onError={_onVocalError}
|
|
39
|
+
commands={commands}
|
|
40
|
+
onStart={() => _log('start')}
|
|
41
|
+
onEnd={() => _log('end')}
|
|
42
|
+
onResult={(result) => _log(`result: "${result}"`)}
|
|
43
|
+
onError={(e) => _log(`error: ${e.message}`)}
|
|
44
|
+
maxAlternatives={3}
|
|
39
45
|
/>
|
|
40
|
-
<
|
|
46
|
+
<p style={{ fontSize: 12, color: '#666', margin: '8px 0' }}>
|
|
47
|
+
Commandes :{' '}
|
|
48
|
+
{Object.keys(COMMANDS).map((k, i) => (
|
|
49
|
+
<span key={k}>{i > 0 && ', '}<code>{k}</code></span>
|
|
50
|
+
))}
|
|
51
|
+
{' '}— ou dans une phrase (ex : « je veux du vert »)
|
|
52
|
+
</p>
|
|
53
|
+
<textarea value={logs} rows={30} disabled style={{ width: '100%', borderColor }} />
|
|
41
54
|
</>
|
|
42
55
|
)
|
|
43
56
|
}
|