From 9fff4f49a4a4b22ed174f65344f61d73cc55d688 Mon Sep 17 00:00:00 2001 From: Julien Odent Date: Thu, 17 Mar 2022 10:19:26 -0700 Subject: [PATCH] Live transcription support Summary: * emits `partialTranscription` and `fullTranscription` events for `speech()` * `interactive` integration * for simplicity, dup the response handler into 2 promises so we don't have to set up timeouts waiting for a final payload before resolving * showing microphone input feedback in `interactive` * including proxy support for `speech()` Reviewed By: ruoyipu Differential Revision: D34938698 fbshipit-source-id: ec0ac55c8778be8aff4de8b38c6a840f663ca28b --- CHANGES.md | 6 +++++ README.md | 3 +++ lib/interactive.js | 8 +++++++ lib/wit.js | 58 +++++++++++++++++++++++++++++++++++++--------- package.json | 2 +- tests/shared.js | 1 - 6 files changed, 65 insertions(+), 13 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3462ece..44bf101 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,9 @@ +## v6.2.1 + +- Emits `partialTranscription` and `fullTranscription` events. +- Shows microphone input feedback for `interactive`. +- Includes `proxy` support for `speech()`. + ## v6.2.0 Requires Node.js >= 6.17.1 to support ES6 directly. diff --git a/README.md b/README.md index 391bfd2..5f30af8 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,9 @@ Takes the following paramters: - `context` - (optional) the [Context](https://wit.ai/docs/http/#context_link) object - `n` - (optional) the max number of intents and traits to get back +Emits `partialTranscription` and `fullTranscription` events. +The Promise returns the final JSON payload. + See `lib/interactive.js` for an example. ### interactive diff --git a/lib/interactive.js b/lib/interactive.js index fe3324d..e7db41b 100644 --- a/lib/interactive.js +++ b/lib/interactive.js @@ -33,6 +33,13 @@ module.exports = (wit, handleResponse, context) => { prompt(); }; + wit.on('partialTranscription', text => { + console.log(text + '...'); + }); + wit.on('fullTranscription', text => { + console.log(text + ' (final)'); + }); + rl.on('line', line => { line = line.trim(); if (!line) { @@ -72,6 +79,7 @@ module.exports = (wit, handleResponse, context) => { }); microphone.start(); + console.log('🎤 Listening...'); return; } diff --git a/lib/wit.js b/lib/wit.js index 729446f..e4f1d2c 100644 --- a/lib/wit.js +++ b/lib/wit.js @@ -7,12 +7,14 @@ const {DEFAULT_API_VERSION, DEFAULT_WIT_URL} = require('./config'); const log = require('./log'); const fetch = require('isomorphic-fetch'); -const Url = require('url'); +const EventEmitter = require('events'); const HttpsProxyAgent = require('https-proxy-agent'); const {Readable} = require('stream'); +const Url = require('url'); -class Wit { +class Wit extends EventEmitter { constructor(opts) { + super(); this.config = Object.freeze(validate(opts)); } @@ -76,23 +78,51 @@ class Wit { const fullURL = witURL + '/speech?' + encodeURIParams(params); logger.debug(method, fullURL); - return fetch(fullURL, { + const req = fetch(fullURL, { body, method, + proxy, headers: { ...headers, 'Content-Type': contentType, 'Transfer-Encoding': 'chunked', }, - }) + }); + + const _partialResponses = req + .then( + response => + new Promise((resolve, reject) => { + logger.debug('status', response.status); + const bodyStream = response.body; + bodyStream.on('readable', () => { + let chunk; + let contents = ''; + while (null !== (chunk = bodyStream.read())) { + contents += chunk.toString(); + } + for (const {error, intents, text} of splitHttpChunks( + contents, + ).map(x => JSON.parse(x))) { + if (!(error || intents)) { + logger.debug('[speech] partialTranscription:', text); + this.emit('partialTranscription', text); + } else if (text) { + logger.debug('[speech] fullTranscription:', text); + this.emit('fullTranscription', text); + } + } + }); + }), + ) + .catch(e => logger.error('[speech] could not parse partial response', e)); + + return req .then(response => Promise.all([response.text(), response.status])) - .then(([contents, status]) => { - const chunks = contents - .split('\r\n') - .map(x => x.trim()) - .filter(x => x.length > 0); - return [JSON.parse(chunks[chunks.length - 1]), status]; - }) + .then(([contents, status]) => [ + JSON.parse(splitHttpChunks(contents).pop()), + status, + ]) .catch(e => e) .then(makeWitResponseHandler(logger, 'speech')); } @@ -147,6 +177,12 @@ const encodeURIParams = params => .map(([key, value]) => key + '=' + encodeURIComponent(value)) .join('&'); +const splitHttpChunks = response => + response + .split('\r\n') + .map(x => x.trim()) + .filter(x => x.length > 0); + const validate = opts => { if (!opts.accessToken) { throw new Error( diff --git a/package.json b/package.json index e744dca..04ddf74 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-wit", - "version": "6.2.0", + "version": "6.2.1", "description": "Wit.ai Node.js SDK", "keywords": [ "wit", diff --git a/tests/shared.js b/tests/shared.js index d32f17e..06c2219 100644 --- a/tests/shared.js +++ b/tests/shared.js @@ -65,7 +65,6 @@ module.exports.runTests = wit => { }); it('tests that Wit has correct functions', () => { - expect(Object.keys(client)).to.eql(['config']); expect(typeof client.message).to.eql('function'); expect(typeof client.speech).to.eql('function'); });