Skip to content

Commit abfae20

Browse files
committed
feat(voicerec): add mode and topic config to node definition
1 parent 3bcb1f6 commit abfae20

File tree

1 file changed

+67
-8
lines changed

1 file changed

+67
-8
lines changed

src/nodes/browser/voicerec.jsx

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,26 @@ export const voicerecNode = {
1515
color: '#ffb6c1', // light pink
1616
icon: true,
1717
faChar: '\uf130', // microphone
18-
inputs: 0,
18+
inputs: 1,
1919
outputs: 1,
2020

2121
defaults: {
22+
mode: {
23+
type: 'select',
24+
default: 'continuous',
25+
label: 'Mode',
26+
options: [
27+
{ value: 'continuous', label: 'Continuous' },
28+
{ value: 'push-to-talk', label: 'Push-to-talk' }
29+
],
30+
description: 'Continuous: always listening. Push-to-talk: only records while receiving "push" signal.'
31+
},
32+
topic: {
33+
type: 'string',
34+
default: '',
35+
label: 'Topic',
36+
description: 'Optional topic for p2p routing. Leave empty for default "voicerec" topic.'
37+
},
2238
lang: { type: 'select', default: 'en-US', label: 'Language', options: [
2339
{ value: 'en-US', label: 'English (US)' },
2440
{ value: 'en-GB', label: 'English (UK)' },
@@ -34,7 +50,7 @@ export const voicerecNode = {
3450
continuous: {
3551
type: 'boolean',
3652
default: true,
37-
label: 'Continuous',
53+
label: 'Auto-restart',
3854
description: 'Keep listening after each result instead of stopping. Turn off for single-phrase recognition.'
3955
},
4056
interimResults: {
@@ -46,11 +62,21 @@ export const voicerecNode = {
4662
},
4763

4864
messageInterface: {
65+
reads: {
66+
payload: {
67+
type: 'string',
68+
description: 'Control signal: "start", "stop", "push", or "release"'
69+
}
70+
},
4971
writes: {
5072
payload: {
5173
type: 'string',
5274
description: 'Transcribed text'
5375
},
76+
topic: {
77+
type: 'string',
78+
description: 'Topic for p2p routing (from config or default "voicerec")'
79+
},
5480
confidence: {
5581
type: 'number',
5682
description: 'Recognition confidence (0-1)'
@@ -63,19 +89,25 @@ export const voicerecNode = {
6389
},
6490

6591
mainThread: (() => {
66-
// Track recognition instance and session ID per node
92+
// Track recognition instance, session ID, and config per node
6793
const voiceRecognitions = new Map();
6894
const nodeSessions = new Map(); // nodeId -> current session ID
95+
const nodeConfigs = new Map(); // nodeId -> { topic, mode, autoRestart }
6996

7097
return {
71-
start(peerRef, nodeId, { lang, continuous: _continuous, interimResults }, PN) {
98+
start(peerRef, nodeId, { lang, topic, mode, autoRestart, interimResults }, PN) {
7299
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
73100
if (!SpeechRecognition) {
74101
peerRef.current.methods.emitEvent(nodeId, 'status', { text: 'Not supported', fill: 'red' });
75102
peerRef.current.methods.emitEvent(nodeId, 'error', 'Speech recognition not supported');
76103
return;
77104
}
78105

106+
// Store config for this node (topic defaults to 'voicerec' if empty)
107+
const effectiveTopic = topic && topic.trim() ? topic.trim() : 'voicerec';
108+
const isPushToTalk = mode === 'push-to-talk';
109+
nodeConfigs.set(nodeId, { topic: effectiveTopic, mode, autoRestart: !isPushToTalk && autoRestart });
110+
79111
// Create a new session ID - this invalidates any previous session's onend handlers
80112
const sessionId = Date.now() + Math.random();
81113
nodeSessions.set(nodeId, sessionId);
@@ -100,18 +132,20 @@ export const voicerecNode = {
100132
recognition.onstart = () => {
101133
// Only update status if this is still the active session
102134
if (nodeSessions.get(nodeId) === sessionId) {
103-
peerRef.current.methods.emitEvent(nodeId, 'status', { text: 'Listening', fill: 'green' });
135+
const statusText = isPushToTalk ? 'Recording' : 'Listening';
136+
peerRef.current.methods.emitEvent(nodeId, 'status', { text: statusText, fill: 'green' });
104137
}
105138
};
106139

107140
recognition.onresult = (event) => {
108141
// Only send results if this is still the active session
109142
if (nodeSessions.get(nodeId) !== sessionId) return;
110143

144+
const config = nodeConfigs.get(nodeId) || { topic: 'voicerec' };
111145
const result = event.results[0][0];
112146
peerRef.current.methods.sendResult(nodeId, {
113147
payload: result.transcript,
114-
topic: 'voicerec',
148+
topic: config.topic,
115149
confidence: result.confidence,
116150
isFinal: true
117151
});
@@ -121,7 +155,7 @@ export const voicerecNode = {
121155
// Ignore errors from stale sessions
122156
if (nodeSessions.get(nodeId) !== sessionId) return;
123157

124-
// 'no-speech' means silence timeout - just restart
158+
// 'no-speech' means silence timeout - just restart (unless PTT)
125159
if (event.error === 'no-speech') {
126160
return;
127161
}
@@ -140,6 +174,14 @@ export const voicerecNode = {
140174
return; // Stale session, ignore
141175
}
142176

177+
const config = nodeConfigs.get(nodeId) || { autoRestart: true };
178+
179+
// In push-to-talk mode, don't auto-restart - wait for next push
180+
if (!config.autoRestart) {
181+
peerRef.current.methods.emitEvent(nodeId, 'status', { text: 'Ready', fill: 'yellow' });
182+
return;
183+
}
184+
143185
// Restart with small delay to prevent rapid loop
144186
setTimeout(() => {
145187
// Double-check we're still the active session after delay
@@ -178,16 +220,33 @@ export const voicerecNode = {
178220
<>
179221
<p>Speech recognition - converts speech to text using the Web Speech API.</p>
180222

223+
<h5>Modes</h5>
224+
<ul>
225+
<li><strong>Continuous</strong> - Always listening, auto-restarts after each result</li>
226+
<li><strong>Push-to-talk</strong> - Only records while receiving "push" signal, stops on "release"</li>
227+
</ul>
228+
181229
<h5>Options</h5>
182230
<ul>
231+
<li><strong>Mode</strong> - Continuous or push-to-talk operation</li>
232+
<li><strong>Topic</strong> - Optional topic for p2p routing (default: "voicerec")</li>
183233
<li><strong>Language</strong> - Recognition language</li>
184-
<li><strong>Continuous</strong> - Keep listening after first result</li>
234+
<li><strong>Auto-restart</strong> - Keep listening after first result (continuous mode only)</li>
185235
<li><strong>Interim Results</strong> - Output partial results while speaking</li>
186236
</ul>
187237

238+
<h5>Input</h5>
239+
<ul>
240+
<li><code>"start"</code> - Start listening</li>
241+
<li><code>"stop"</code> - Stop listening</li>
242+
<li><code>"push"</code> - Start recording (push-to-talk mode)</li>
243+
<li><code>"release"</code> - Stop recording (push-to-talk mode)</li>
244+
</ul>
245+
188246
<h5>Output</h5>
189247
<ul>
190248
<li><code>msg.payload</code> - Transcribed text</li>
249+
<li><code>msg.topic</code> - Topic for p2p routing</li>
191250
<li><code>msg.confidence</code> - Recognition confidence (0-1)</li>
192251
<li><code>msg.isFinal</code> - Whether this is a final or interim result</li>
193252
</ul>

0 commit comments

Comments
 (0)