@volley/recognition-client-sdk-node22 0.1.424
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/README.md +344 -0
- package/dist/browser.bundled.d.ts +1280 -0
- package/dist/browser.d.ts +10 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/config-builder.d.ts +134 -0
- package/dist/config-builder.d.ts.map +1 -0
- package/dist/errors.d.ts +41 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/factory.d.ts +36 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/index.bundled.d.ts +2572 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10199 -0
- package/dist/index.js.map +7 -0
- package/dist/recog-client-sdk.browser.d.ts +10 -0
- package/dist/recog-client-sdk.browser.d.ts.map +1 -0
- package/dist/recog-client-sdk.browser.js +5746 -0
- package/dist/recog-client-sdk.browser.js.map +7 -0
- package/dist/recognition-client.d.ts +128 -0
- package/dist/recognition-client.d.ts.map +1 -0
- package/dist/recognition-client.types.d.ts +271 -0
- package/dist/recognition-client.types.d.ts.map +1 -0
- package/dist/simplified-vgf-recognition-client.d.ts +178 -0
- package/dist/simplified-vgf-recognition-client.d.ts.map +1 -0
- package/dist/utils/audio-ring-buffer.d.ts +69 -0
- package/dist/utils/audio-ring-buffer.d.ts.map +1 -0
- package/dist/utils/message-handler.d.ts +45 -0
- package/dist/utils/message-handler.d.ts.map +1 -0
- package/dist/utils/url-builder.d.ts +28 -0
- package/dist/utils/url-builder.d.ts.map +1 -0
- package/dist/vgf-recognition-mapper.d.ts +66 -0
- package/dist/vgf-recognition-mapper.d.ts.map +1 -0
- package/dist/vgf-recognition-state.d.ts +91 -0
- package/dist/vgf-recognition-state.d.ts.map +1 -0
- package/package.json +74 -0
- package/src/browser.ts +24 -0
- package/src/config-builder.spec.ts +265 -0
- package/src/config-builder.ts +240 -0
- package/src/errors.ts +84 -0
- package/src/factory.spec.ts +215 -0
- package/src/factory.ts +47 -0
- package/src/index.ts +127 -0
- package/src/recognition-client.spec.ts +889 -0
- package/src/recognition-client.ts +844 -0
- package/src/recognition-client.types.ts +338 -0
- package/src/simplified-vgf-recognition-client.integration.spec.ts +718 -0
- package/src/simplified-vgf-recognition-client.spec.ts +1525 -0
- package/src/simplified-vgf-recognition-client.ts +524 -0
- package/src/utils/audio-ring-buffer.spec.ts +335 -0
- package/src/utils/audio-ring-buffer.ts +170 -0
- package/src/utils/message-handler.spec.ts +311 -0
- package/src/utils/message-handler.ts +131 -0
- package/src/utils/url-builder.spec.ts +252 -0
- package/src/utils/url-builder.ts +92 -0
- package/src/vgf-recognition-mapper.spec.ts +78 -0
- package/src/vgf-recognition-mapper.ts +232 -0
- package/src/vgf-recognition-state.ts +102 -0
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simplified VGF Recognition Client
|
|
3
|
+
*
|
|
4
|
+
* A thin wrapper around RealTimeTwoWayWebSocketRecognitionClient that maintains
|
|
5
|
+
* a VGF RecognitionState as a pure sink/output of recognition events.
|
|
6
|
+
*
|
|
7
|
+
* The VGF state is updated based on events but never influences client behavior.
|
|
8
|
+
* All functionality is delegated to the underlying client.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
RecognitionState,
|
|
13
|
+
TranscriptionStatus,
|
|
14
|
+
RecordingStatus,
|
|
15
|
+
RecognitionActionProcessingState
|
|
16
|
+
} from './vgf-recognition-state.js';
|
|
17
|
+
import {
|
|
18
|
+
IRecognitionClient,
|
|
19
|
+
IRecognitionClientConfig,
|
|
20
|
+
ClientState
|
|
21
|
+
} from './recognition-client.types.js';
|
|
22
|
+
import { RealTimeTwoWayWebSocketRecognitionClient } from './recognition-client.js';
|
|
23
|
+
import {
|
|
24
|
+
createVGFStateFromConfig,
|
|
25
|
+
mapTranscriptionResultToState,
|
|
26
|
+
mapErrorToState,
|
|
27
|
+
updateStateOnStop,
|
|
28
|
+
resetRecognitionVGFState
|
|
29
|
+
} from './vgf-recognition-mapper.js';
|
|
30
|
+
import { RecognitionContextTypeV1 } from '@recog/shared-types';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Configuration for SimplifiedVGFRecognitionClient
|
|
34
|
+
*/
|
|
35
|
+
export interface SimplifiedVGFClientConfig extends IRecognitionClientConfig {
|
|
36
|
+
/**
|
|
37
|
+
* Callback invoked whenever the VGF state changes
|
|
38
|
+
* Use this to update your UI or React state
|
|
39
|
+
*/
|
|
40
|
+
onStateChange?: (state: RecognitionState) => void;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Optional initial state to restore from a previous session
|
|
44
|
+
* If provided, audioUtteranceId will be extracted and used
|
|
45
|
+
*/
|
|
46
|
+
initialState?: RecognitionState;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Interface for SimplifiedVGFRecognitionClient
|
|
51
|
+
*
|
|
52
|
+
* A simplified client that maintains VGF state for game developers.
|
|
53
|
+
* All methods from the underlying client are available, plus VGF state management.
|
|
54
|
+
*/
|
|
55
|
+
export interface ISimplifiedVGFRecognitionClient {
|
|
56
|
+
// ============= Core Connection Methods =============
|
|
57
|
+
/**
|
|
58
|
+
* Connect to the recognition service WebSocket
|
|
59
|
+
* @returns Promise that resolves when connected and ready
|
|
60
|
+
*/
|
|
61
|
+
connect(): Promise<void>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Send audio data for transcription
|
|
65
|
+
* @param audioData - PCM audio data as ArrayBuffer, typed array, or Blob
|
|
66
|
+
*/
|
|
67
|
+
sendAudio(audioData: ArrayBuffer | ArrayBufferView | Blob): void;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Stop recording and wait for final transcription
|
|
71
|
+
* @returns Promise that resolves when transcription is complete
|
|
72
|
+
*/
|
|
73
|
+
stopRecording(): Promise<void>;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Force stop and immediately close connection without waiting for server
|
|
77
|
+
*
|
|
78
|
+
* WARNING: This is an abnormal shutdown that bypasses the graceful stop flow:
|
|
79
|
+
* - Does NOT wait for server to process remaining audio
|
|
80
|
+
* - Does NOT receive final transcript from server (VGF state set to empty)
|
|
81
|
+
* - Immediately closes WebSocket connection
|
|
82
|
+
* - Cleans up resources (buffers, listeners)
|
|
83
|
+
*
|
|
84
|
+
* Use Cases:
|
|
85
|
+
* - User explicitly cancels/abandons the session
|
|
86
|
+
* - Timeout scenarios where waiting is not acceptable
|
|
87
|
+
* - Need immediate cleanup and can't wait for server
|
|
88
|
+
*
|
|
89
|
+
* RECOMMENDED: Use stopRecording() for normal shutdown.
|
|
90
|
+
* Only use this when immediate disconnection is required.
|
|
91
|
+
*/
|
|
92
|
+
stopAbnormally(): void;
|
|
93
|
+
|
|
94
|
+
// ============= VGF State Methods =============
|
|
95
|
+
/**
|
|
96
|
+
* Get the current VGF recognition state
|
|
97
|
+
* @returns Current RecognitionState with all transcription data
|
|
98
|
+
*/
|
|
99
|
+
getVGFState(): RecognitionState;
|
|
100
|
+
|
|
101
|
+
// ============= Status Check Methods =============
|
|
102
|
+
/**
|
|
103
|
+
* Check if connected to the WebSocket
|
|
104
|
+
*/
|
|
105
|
+
isConnected(): boolean;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Check if currently connecting
|
|
109
|
+
*/
|
|
110
|
+
isConnecting(): boolean;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Check if currently stopping
|
|
114
|
+
*/
|
|
115
|
+
isStopping(): boolean;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Check if transcription has finished
|
|
119
|
+
*/
|
|
120
|
+
isTranscriptionFinished(): boolean;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Check if the audio buffer has overflowed
|
|
124
|
+
*/
|
|
125
|
+
isBufferOverflowing(): boolean;
|
|
126
|
+
|
|
127
|
+
// ============= Utility Methods =============
|
|
128
|
+
/**
|
|
129
|
+
* Get the audio utterance ID for this session
|
|
130
|
+
*/
|
|
131
|
+
getAudioUtteranceId(): string;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get the WebSocket URL being used
|
|
135
|
+
*/
|
|
136
|
+
getUrl(): string;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get the underlying client state (for advanced usage)
|
|
140
|
+
*/
|
|
141
|
+
getState(): ClientState;
|
|
142
|
+
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* This wrapper ONLY maintains VGF state as a sink.
|
|
147
|
+
* All actual functionality is delegated to the underlying client.
|
|
148
|
+
*/
|
|
149
|
+
export class SimplifiedVGFRecognitionClient implements ISimplifiedVGFRecognitionClient {
|
|
150
|
+
private client: IRecognitionClient;
|
|
151
|
+
private state: RecognitionState;
|
|
152
|
+
private isRecordingAudio: boolean = false;
|
|
153
|
+
private stateChangeCallback: ((state: RecognitionState) => void) | undefined;
|
|
154
|
+
private expectedUuid: string;
|
|
155
|
+
private logger: IRecognitionClientConfig['logger'];
|
|
156
|
+
private lastSentTerminalUuid: string | null = null;
|
|
157
|
+
|
|
158
|
+
constructor(config: SimplifiedVGFClientConfig) {
|
|
159
|
+
const { onStateChange, initialState, ...clientConfig } = config;
|
|
160
|
+
this.stateChangeCallback = onStateChange;
|
|
161
|
+
this.logger = clientConfig.logger;
|
|
162
|
+
|
|
163
|
+
// Use provided initial state or create from config
|
|
164
|
+
if (initialState) {
|
|
165
|
+
// Check if initial state has a valid UUID
|
|
166
|
+
const needsNewUuid = !initialState.audioUtteranceId ||
|
|
167
|
+
initialState.audioUtteranceId === '' ||
|
|
168
|
+
initialState.transcriptionStatus === TranscriptionStatus.ABORTED ||
|
|
169
|
+
initialState.transcriptionStatus === TranscriptionStatus.FINALIZED ||
|
|
170
|
+
initialState.transcriptionStatus === TranscriptionStatus.ERROR ||
|
|
171
|
+
(initialState.recognitionActionProcessingState !== undefined && initialState.recognitionActionProcessingState !== RecognitionActionProcessingState.COMPLETED);
|
|
172
|
+
if (needsNewUuid) {
|
|
173
|
+
// Reset session state with new UUID
|
|
174
|
+
this.state = resetRecognitionVGFState(initialState);
|
|
175
|
+
const newUUID = this.state.audioUtteranceId;
|
|
176
|
+
|
|
177
|
+
if (clientConfig.logger) {
|
|
178
|
+
const reason = !initialState.audioUtteranceId ? 'Missing UUID' :
|
|
179
|
+
initialState.audioUtteranceId === '' ? 'Empty UUID' :
|
|
180
|
+
`Terminal session (${initialState.transcriptionStatus})`;
|
|
181
|
+
clientConfig.logger('info', `${reason} detected, generating new UUID: ${newUUID}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Use new UUID in client config
|
|
185
|
+
clientConfig.audioUtteranceId = newUUID;
|
|
186
|
+
|
|
187
|
+
// Reset terminal status tracking for new session
|
|
188
|
+
this.lastSentTerminalUuid = null;
|
|
189
|
+
|
|
190
|
+
// Notify state change immediately so app can update
|
|
191
|
+
if (onStateChange) {
|
|
192
|
+
onStateChange(this.state);
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
// Non-terminal state with valid UUID - safe to reuse (e.g., reconnecting to IN_PROGRESS session)
|
|
196
|
+
this.state = initialState;
|
|
197
|
+
// Override audioUtteranceId in config if state has one
|
|
198
|
+
if (initialState.audioUtteranceId && !clientConfig.audioUtteranceId) {
|
|
199
|
+
clientConfig.audioUtteranceId = initialState.audioUtteranceId;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
// Initialize VGF state from config
|
|
204
|
+
this.state = createVGFStateFromConfig(clientConfig);
|
|
205
|
+
// Ensure clientConfig uses the same UUID as VGF state
|
|
206
|
+
clientConfig.audioUtteranceId = this.state.audioUtteranceId;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Client is immediately ready to accept audio (will buffer if not connected)
|
|
210
|
+
this.state = { ...this.state, startRecordingStatus: 'READY' };
|
|
211
|
+
|
|
212
|
+
// Track the expected UUID for this session
|
|
213
|
+
this.expectedUuid = this.state.audioUtteranceId;
|
|
214
|
+
|
|
215
|
+
// If VGF state has promptSlotMap, configure gameContext to use it
|
|
216
|
+
if (this.state.promptSlotMap) {
|
|
217
|
+
// Set useContext=true in ASR config to enable context processing
|
|
218
|
+
if (clientConfig.asrRequestConfig) {
|
|
219
|
+
clientConfig.asrRequestConfig.useContext = true;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Add promptSlotMap to gameContext
|
|
223
|
+
if (!clientConfig.gameContext) {
|
|
224
|
+
// Only create gameContext if we have gameId and gamePhase
|
|
225
|
+
// These should come from the game's configuration
|
|
226
|
+
if (clientConfig.logger) {
|
|
227
|
+
clientConfig.logger('warn', '[VGF] promptSlotMap found but no gameContext provided. SlotMap will not be sent.');
|
|
228
|
+
}
|
|
229
|
+
} else {
|
|
230
|
+
// Merge promptSlotMap into existing gameContext
|
|
231
|
+
clientConfig.gameContext.slotMap = this.state.promptSlotMap;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Create underlying client with callbacks that ONLY update VGF state
|
|
236
|
+
this.client = new RealTimeTwoWayWebSocketRecognitionClient({
|
|
237
|
+
...clientConfig,
|
|
238
|
+
|
|
239
|
+
// These callbacks ONLY update the VGF state sink
|
|
240
|
+
onTranscript: (result) => {
|
|
241
|
+
// Skip update if UUID doesn't match (stale callback from previous session)
|
|
242
|
+
if (result.audioUtteranceId && result.audioUtteranceId !== this.expectedUuid) {
|
|
243
|
+
if (this.logger) {
|
|
244
|
+
this.logger('warn',
|
|
245
|
+
`[RecogSDK:VGF] Skipping transcript update: UUID mismatch (expected: ${this.expectedUuid}, got: ${result.audioUtteranceId})`
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Update VGF state based on transcript (always update local state)
|
|
252
|
+
this.state = mapTranscriptionResultToState(this.state, result, this.isRecordingAudio);
|
|
253
|
+
this.notifyStateChange();
|
|
254
|
+
|
|
255
|
+
// Call original callback if provided
|
|
256
|
+
if (clientConfig.onTranscript) {
|
|
257
|
+
clientConfig.onTranscript(result);
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
onMetadata: (metadata) => {
|
|
262
|
+
// Skip update if UUID doesn't match (stale callback from previous session)
|
|
263
|
+
if (metadata.audioUtteranceId && metadata.audioUtteranceId !== this.expectedUuid) {
|
|
264
|
+
if (this.logger) {
|
|
265
|
+
this.logger('warn',
|
|
266
|
+
`[RecogSDK:VGF] Skipping metadata update: UUID mismatch (expected: ${this.expectedUuid}, got: ${metadata.audioUtteranceId})`
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (clientConfig.onMetadata) {
|
|
273
|
+
clientConfig.onMetadata(metadata);
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
onFunctionCall: (result) => {
|
|
278
|
+
// Pass through function call - no VGF state changes needed for P2 feature
|
|
279
|
+
if (clientConfig.onFunctionCall) {
|
|
280
|
+
clientConfig.onFunctionCall(result);
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
onError: (error) => {
|
|
285
|
+
// Skip update if UUID doesn't match (stale callback from previous session)
|
|
286
|
+
if (error.audioUtteranceId && error.audioUtteranceId !== this.expectedUuid) {
|
|
287
|
+
if (this.logger) {
|
|
288
|
+
this.logger('warn',
|
|
289
|
+
`[RecogSDK:VGF] Skipping error update: UUID mismatch (expected: ${this.expectedUuid}, got: ${error.audioUtteranceId})`
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
this.isRecordingAudio = false; // Reset on error
|
|
296
|
+
this.state = mapErrorToState(this.state, error);
|
|
297
|
+
this.notifyStateChange();
|
|
298
|
+
|
|
299
|
+
if (clientConfig.onError) {
|
|
300
|
+
clientConfig.onError(error);
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
onConnected: () => {
|
|
305
|
+
// Don't update READY here - client can accept audio before connection
|
|
306
|
+
if (clientConfig.onConnected) {
|
|
307
|
+
clientConfig.onConnected();
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
|
|
311
|
+
onDisconnected: (code, reason) => {
|
|
312
|
+
this.isRecordingAudio = false; // Reset on disconnect
|
|
313
|
+
if (clientConfig.onDisconnected) {
|
|
314
|
+
clientConfig.onDisconnected(code, reason);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// DELEGATE ALL METHODS TO UNDERLYING CLIENT
|
|
321
|
+
// The wrapper ONLY updates VGF state, doesn't use it for decisions
|
|
322
|
+
|
|
323
|
+
async connect(): Promise<void> {
|
|
324
|
+
await this.client.connect();
|
|
325
|
+
// State will be updated via onConnected callback
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
sendAudio(audioData: ArrayBuffer | ArrayBufferView | Blob): void {
|
|
329
|
+
// Track recording for state updates
|
|
330
|
+
if (!this.isRecordingAudio) {
|
|
331
|
+
this.isRecordingAudio = true;
|
|
332
|
+
this.state = {
|
|
333
|
+
...this.state,
|
|
334
|
+
startRecordingStatus: 'RECORDING',
|
|
335
|
+
startRecordingTimestamp: new Date().toISOString()
|
|
336
|
+
};
|
|
337
|
+
this.notifyStateChange();
|
|
338
|
+
}
|
|
339
|
+
this.client.sendAudio(audioData);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async stopRecording(): Promise<void> {
|
|
343
|
+
this.isRecordingAudio = false;
|
|
344
|
+
this.state = updateStateOnStop(this.state);
|
|
345
|
+
this.notifyStateChange();
|
|
346
|
+
|
|
347
|
+
// Early termination: If connection is not yet established with server, emit synthetic finalization immediately
|
|
348
|
+
// This prevents games from getting stuck waiting for a server response that may never come
|
|
349
|
+
if (this.client.getState() === ClientState.CONNECTED || this.client.getState() === ClientState.CONNECTING) {
|
|
350
|
+
if (this.logger) {
|
|
351
|
+
this.logger('info',
|
|
352
|
+
`[RecogSDK:VGF] Early termination detected (transcriptionStatus: NOT_STARTED) - emitting synthetic finalization`
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
this.state = {
|
|
356
|
+
...this.state,
|
|
357
|
+
transcriptionStatus: TranscriptionStatus.FINALIZED,
|
|
358
|
+
finalTranscript: '',
|
|
359
|
+
finalConfidence: 0,
|
|
360
|
+
pendingTranscript: '',
|
|
361
|
+
pendingConfidence: undefined,
|
|
362
|
+
finalTranscriptionTimestamp: new Date().toISOString()
|
|
363
|
+
};
|
|
364
|
+
this.notifyStateChange();
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
await this.client.stopRecording();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
stopAbnormally(): void {
|
|
371
|
+
const clientState = this.client.getState();
|
|
372
|
+
|
|
373
|
+
// Guard: Block if graceful shutdown in progress or already in terminal state
|
|
374
|
+
// This prevents stopAbnormally from disrupting stopRecording's graceful finalization
|
|
375
|
+
if (clientState === ClientState.STOPPING ||
|
|
376
|
+
clientState === ClientState.STOPPED ||
|
|
377
|
+
clientState === ClientState.FAILED) {
|
|
378
|
+
// Already stopping/stopped - do nothing to avoid disrupting graceful shutdown
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
this.isRecordingAudio = false;
|
|
383
|
+
|
|
384
|
+
// Set state to ABORTED - preserve any partial transcript received so far
|
|
385
|
+
// This clearly indicates the session was cancelled/abandoned by user
|
|
386
|
+
if (this.state.transcriptionStatus !== TranscriptionStatus.ABORTED &&
|
|
387
|
+
this.state.transcriptionStatus !== TranscriptionStatus.FINALIZED) {
|
|
388
|
+
this.state = {
|
|
389
|
+
...this.state,
|
|
390
|
+
transcriptionStatus: TranscriptionStatus.ABORTED,
|
|
391
|
+
startRecordingStatus: RecordingStatus.FINISHED,
|
|
392
|
+
finalRecordingTimestamp: new Date().toISOString(),
|
|
393
|
+
finalTranscriptionTimestamp: new Date().toISOString()
|
|
394
|
+
};
|
|
395
|
+
this.notifyStateChange();
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Delegate to underlying client for actual WebSocket cleanup
|
|
399
|
+
this.client.stopAbnormally();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Pure delegation methods - no state logic
|
|
403
|
+
getAudioUtteranceId(): string {
|
|
404
|
+
return this.client.getAudioUtteranceId();
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
getUrl(): string {
|
|
408
|
+
return this.client.getUrl();
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
getState(): ClientState {
|
|
412
|
+
return this.client.getState();
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
isConnected(): boolean {
|
|
416
|
+
return this.client.isConnected();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
isConnecting(): boolean {
|
|
420
|
+
return this.client.isConnecting();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
isStopping(): boolean {
|
|
424
|
+
return this.client.isStopping();
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
isTranscriptionFinished(): boolean {
|
|
428
|
+
return this.client.isTranscriptionFinished();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
isBufferOverflowing(): boolean {
|
|
432
|
+
return this.client.isBufferOverflowing();
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
// VGF State access (read-only for consumers)
|
|
437
|
+
getVGFState(): RecognitionState {
|
|
438
|
+
return { ...this.state };
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
private isTerminalStatus(status: string | undefined): boolean {
|
|
442
|
+
return status === TranscriptionStatus.FINALIZED ||
|
|
443
|
+
status === TranscriptionStatus.ABORTED ||
|
|
444
|
+
status === TranscriptionStatus.ERROR;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
private notifyStateChange(): void {
|
|
448
|
+
|
|
449
|
+
// Block duplicate terminal status emissions for THIS session
|
|
450
|
+
if (this.isTerminalStatus(this.state.transcriptionStatus)) {
|
|
451
|
+
if (this.lastSentTerminalUuid === this.expectedUuid) {
|
|
452
|
+
// Already sent a terminal status for this session - suppress duplicate
|
|
453
|
+
if (this.logger) {
|
|
454
|
+
this.logger('info',
|
|
455
|
+
`[RecogSDK:VGF] Duplicate terminal status suppressed (lastSentTerminalUuid: ${this.lastSentTerminalUuid})`,
|
|
456
|
+
{ transcriptionStatus: this.state.transcriptionStatus, finalTranscript: this.state.finalTranscript }
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
// First terminal status for this session - record it
|
|
462
|
+
this.lastSentTerminalUuid = this.expectedUuid;
|
|
463
|
+
if (this.logger) {
|
|
464
|
+
this.logger('info',
|
|
465
|
+
`[RecogSDK:VGF] Sending terminal status (uuid: ${this.expectedUuid})`,
|
|
466
|
+
{ transcriptionStatus: this.state.transcriptionStatus, finalTranscript: this.state.finalTranscript }
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (!this.stateChangeCallback) {
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
this.stateChangeCallback({ ...this.state });
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Factory function for creating simplified client
|
|
481
|
+
* Usage examples:
|
|
482
|
+
*
|
|
483
|
+
* // Basic usage
|
|
484
|
+
* const client = createSimplifiedVGFClient({
|
|
485
|
+
* asrRequestConfig: { provider: 'deepgram', language: 'en' },
|
|
486
|
+
* onStateChange: (state) => {
|
|
487
|
+
* console.log('VGF State updated:', state);
|
|
488
|
+
* // Update React state, game UI, etc.
|
|
489
|
+
* }
|
|
490
|
+
* });
|
|
491
|
+
*
|
|
492
|
+
* // With initial state (e.g., restoring from previous session)
|
|
493
|
+
* const client = createSimplifiedVGFClient({
|
|
494
|
+
* asrRequestConfig: { provider: 'deepgram', language: 'en' },
|
|
495
|
+
* initialState: previousState, // Will use audioUtteranceId from state
|
|
496
|
+
* onStateChange: (state) => setVGFState(state)
|
|
497
|
+
* });
|
|
498
|
+
*
|
|
499
|
+
* // With initial state containing promptSlotMap for enhanced recognition
|
|
500
|
+
* const stateWithSlots: RecognitionState = {
|
|
501
|
+
* audioUtteranceId: 'session-123',
|
|
502
|
+
* promptSlotMap: {
|
|
503
|
+
* 'song_title': ['one time', 'baby'],
|
|
504
|
+
* 'artists': ['justin bieber']
|
|
505
|
+
* }
|
|
506
|
+
* };
|
|
507
|
+
* const client = createSimplifiedVGFClient({
|
|
508
|
+
* asrRequestConfig: { provider: 'deepgram', language: 'en' },
|
|
509
|
+
* gameContext: {
|
|
510
|
+
* type: RecognitionContextTypeV1.GAME_CONTEXT,
|
|
511
|
+
* gameId: 'music-quiz', // Your game's ID
|
|
512
|
+
* gamePhase: 'song-guessing' // Current game phase
|
|
513
|
+
* },
|
|
514
|
+
* initialState: stateWithSlots, // promptSlotMap will be added to gameContext
|
|
515
|
+
* onStateChange: (state) => setVGFState(state)
|
|
516
|
+
* });
|
|
517
|
+
*
|
|
518
|
+
* await client.connect();
|
|
519
|
+
* client.sendAudio(audioData);
|
|
520
|
+
* // VGF state automatically updates based on transcription results
|
|
521
|
+
*/
|
|
522
|
+
export function createSimplifiedVGFClient(config: SimplifiedVGFClientConfig): ISimplifiedVGFRecognitionClient {
|
|
523
|
+
return new SimplifiedVGFRecognitionClient(config);
|
|
524
|
+
}
|