@untemps/react-vocal 2.0.0-beta.2 → 2.0.0-beta.4
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 +14 -0
- package/README.md +18 -17
- package/dev/src/index.jsx +39 -26
- package/dist/index.es.js +318 -286
- 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 +1 -1
- package/src/components/Vocal.jsx +35 -8
- package/src/components/__tests__/Vocal.test.jsx +181 -0
- package/src/hooks/__tests__/useCommands.test.js +25 -0
- package/src/hooks/__tests__/useVocal.test.js +6 -1
- package/src/hooks/useCommands.js +34 -6
- package/src/hooks/useVocal.js +3 -3
- package/vitest.setup.js +6 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [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)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* Per-segment command matching, maxAlternatives, and exact lookup ([39b6370](https://github.com/untemps/react-vocal/commit/39b63701b2a15c058b5ae510be8ad4b4437d6763))
|
|
7
|
+
|
|
8
|
+
# [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)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* Aggregate all result segments picking highest-confidence alternative ([#113](https://github.com/untemps/react-vocal/issues/113)) ([600600d](https://github.com/untemps/react-vocal/commit/600600dfde457df6fe93974d76d0b48599846082))
|
|
14
|
+
|
|
1
15
|
# [2.0.0-beta.2](https://github.com/untemps/react-vocal/compare/v2.0.0-beta.1...v2.0.0-beta.2) (2026-05-07)
|
|
2
16
|
|
|
3
17
|
|
package/README.md
CHANGED
|
@@ -190,17 +190,15 @@ const commands = {
|
|
|
190
190
|
}
|
|
191
191
|
```
|
|
192
192
|
|
|
193
|
-
The component utilizes a special hook called `useCommands` to respond to the commands.
|
|
194
|
-
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 instantiantion.
|
|
193
|
+
The component utilizes a special hook called `useCommands` to respond to the commands.
|
|
196
194
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
195
|
+
**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`).
|
|
196
|
+
|
|
197
|
+
**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
198
|
|
|
201
|
-
|
|
199
|
+
**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
200
|
|
|
203
|
-
|
|
201
|
+
**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
202
|
|
|
205
203
|
---
|
|
206
204
|
|
|
@@ -208,10 +206,12 @@ See [fuze.js scoring theory](https://fusejs.io/concepts/scoring-theory.html) for
|
|
|
208
206
|
|
|
209
207
|
| Props | Type | Default | Description |
|
|
210
208
|
| ------------- | ----------------- | -------------------- | ----------------------------------------------------------------------------------------------- |
|
|
211
|
-
| commands
|
|
212
|
-
| lang
|
|
213
|
-
| grammars
|
|
214
|
-
| timeout
|
|
209
|
+
| commands | object | null | Callbacks to be triggered when specified commands are detected by the recognition |
|
|
210
|
+
| lang | string | 'en-US' | Language understood by the recognition [BCP 47 language tag](https://tools.ietf.org/html/bcp47) |
|
|
211
|
+
| grammars | SpeechGrammarList | null | Grammars understood by the recognition [JSpeech Grammar Format](https://www.w3.org/TR/jsgf/) |
|
|
212
|
+
| timeout | number | 3000 | Time in ms to wait before discarding the recognition |
|
|
213
|
+
| precision | number | 0.4 | Fuse.js score threshold for **phrase** command keys only (lower = stricter). Single-word commands always use exact lookup. |
|
|
214
|
+
| 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
215
|
| style | object | null | Styles of the root element if className is not specified |
|
|
216
216
|
| className | string | null | Class of the root element |
|
|
217
217
|
| ariaLabel | string | 'start recognition' | Accessible label for the default button |
|
|
@@ -286,13 +286,14 @@ const App = () => {
|
|
|
286
286
|
#### Signature
|
|
287
287
|
|
|
288
288
|
```
|
|
289
|
-
useVocal(lang, grammars)
|
|
289
|
+
useVocal(lang, grammars, maxAlternatives)
|
|
290
290
|
```
|
|
291
291
|
|
|
292
|
-
| Args
|
|
293
|
-
|
|
|
294
|
-
| lang
|
|
295
|
-
| grammars
|
|
292
|
+
| Args | Type | Default | Description |
|
|
293
|
+
| --------------- | ----------------- | ------- | ----------------------------------------------------------------------------------------------- |
|
|
294
|
+
| lang | string | 'en-US' | Language understood by the recognition [BCP 47 language tag](https://tools.ietf.org/html/bcp47) |
|
|
295
|
+
| grammars | SpeechGrammarList | null | Grammars understood by the recognition [JSpeech Grammar Format](https://www.w3.org/TR/jsgf/) |
|
|
296
|
+
| maxAlternatives | number | 1 | Maximum number of recognition alternatives per segment |
|
|
296
297
|
|
|
297
298
|
---
|
|
298
299
|
|
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
|
}
|