diff --git a/CHANGES.md b/CHANGES.md index e7bb65b..89840da 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,8 @@ +## v6.6.0 - Multi-responses in Composer + +- Multi-responses in Composer: `runComposerAudio()`, `.runComposer()` (and raw `.converse()` + `.event()`) now emit `response` events for intermediate responses, and run intermediate actions as well. +- Bumped API version to `20230215`. + ## v6.5.1 - Update uuid to version 9.0.0 diff --git a/README.md b/README.md index bc34d1f..0f527c2 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,8 @@ Takes the following parameters: - `body` - the audio `Readable` stream - `contextMap` - the [context map](https://wit.ai/docs/recipes#custom-context) object -Emits `partialTranscription` and `fullTranscription` events. +Emits `partialTranscription`, `fullTranscription`, and `response` events. Runs +intermediate `actions` as instructed by the API. We recommend to use `.runComposerAudio()` instead of this raw API. @@ -132,6 +133,8 @@ Takes the following parameters: - `contextMap` - the [context map](https://wit.ai/docs/recipes#custom-context) object - `message` - the optional user text query +Emits `response` events, and run intermediate `actions` as instructed by the API. + We recommend to use `.runComposer()` instead of this raw API. ### .message() diff --git a/lib/config.js b/lib/config.js index e4074a2..d46214e 100644 --- a/lib/config.js +++ b/lib/config.js @@ -3,6 +3,6 @@ */ module.exports = { - DEFAULT_API_VERSION: 20220801, + DEFAULT_API_VERSION: 20230215, DEFAULT_WIT_URL: 'https://api.wit.ai', }; diff --git a/lib/wit.js b/lib/wit.js index b7ac424..1cfa07b 100644 --- a/lib/wit.js +++ b/lib/wit.js @@ -13,6 +13,7 @@ const {Readable} = require('stream'); const Url = require('url'); const LIVE_UNDERSTANDING_API_VERSION = 20220608; +const MULTI_RESPONSES_API_VERSION = 20230215; class Wit extends EventEmitter { constructor(opts) { @@ -45,7 +46,7 @@ class Wit extends EventEmitter { throw new Error('Please provide an audio stream (Readable).'); } - const {apiVersion, headers, logger, proxy, witURL} = this.config; + const {actions, apiVersion, headers, logger, proxy, witURL} = this.config; const params = { session_id: sessionId, @@ -60,6 +61,8 @@ class Wit extends EventEmitter { const fullURL = witURL + '/converse?' + encodeURIParams(params); logger.debug(method, fullURL); + const multi_responses_enabled = apiVersion >= MULTI_RESPONSES_API_VERSION; + const req = fetch(fullURL, { body, method, @@ -73,10 +76,10 @@ class Wit extends EventEmitter { const _partialResponses = req .then( - response => + resp => new Promise((resolve, reject) => { - logger.debug('status', response.status); - const bodyStream = response.body; + logger.debug('status', resp.status); + const bodyStream = resp.body; bodyStream.on('readable', () => { let chunk; @@ -86,13 +89,31 @@ class Wit extends EventEmitter { } for (const rsp of parseResponse(contents)) { - const {error, is_final, text} = rsp; + const {action, context_map, error, is_final, response, text} = + rsp; // Live transcription if (!(error || is_final)) { logger.debug('[converse] partialTranscription:', text); this.emit('partialTranscription', text); } + + // Multi-responses + if ( + multi_responses_enabled && + !(error || is_final) && + (action || response) + ) { + if (response) { + logger.debug('[converse] partialResponse:', response); + this.emit('response', response); + } + + if (action) { + logger.debug('[converse] got partial action:', action); + runAction(logger, actions, action, context_map); + } + } } }); }), @@ -121,7 +142,7 @@ class Wit extends EventEmitter { throw new Error('Please provide a session ID (string).'); } - const {apiVersion, headers, logger, proxy, witURL} = this.config; + const {actions, apiVersion, headers, logger, proxy, witURL} = this.config; const params = { session_id: sessionId, @@ -142,8 +163,56 @@ class Wit extends EventEmitter { const fullURL = witURL + '/event?' + encodeURIParams(params); logger.debug(method, fullURL); - return fetch(fullURL, {body: JSON.stringify(body), method, headers, proxy}) - .then(response => Promise.all([response.json(), response.status])) + const req = fetch(fullURL, { + body: JSON.stringify(body), + method, + headers, + proxy, + }); + + // Multi-responses + if (apiVersion >= MULTI_RESPONSES_API_VERSION) { + const _partialResponses = req + .then( + resp => + new Promise((resolve, reject) => { + logger.debug('status', resp.status); + const bodyStream = resp.body; + + bodyStream.on('readable', () => { + let chunk; + let contents = ''; + while (null !== (chunk = bodyStream.read())) { + contents += chunk.toString(); + } + + for (const rsp of parseResponse(contents)) { + const {action, context_map, error, is_final, response} = rsp; + + if (!(error || is_final) && (action || response)) { + if (response) { + logger.debug('[event] partialResponse:', response); + this.emit('response', response); + } + + if (action) { + logger.debug('[event] got partial action:', action); + runAction(logger, actions, action, context_map); + } + } + } + }); + }), + ) + .catch(e => + logger.error('[event] could not parse partial response', e), + ); + } + + return req + .then(response => Promise.all([response.text(), response.status])) + .then(([contents, status]) => ([parseResponse(contents).pop(), status])) + .catch(e => e) .then(makeWitResponseHandler(logger, 'event')); } @@ -364,7 +433,7 @@ class Wit extends EventEmitter { throw new Error('Please provide a text input (string).'); } if (typeof voice !== 'string') { - throw new Error('Please provide a voice input. (string)'); + throw new Error('Please provide a voice input (string).'); } const {apiVersion, headers, logger, proxy, witURL} = this.config; diff --git a/package.json b/package.json index 3d002fd..ec338cd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-wit", - "version": "6.5.1", + "version": "6.6.0", "description": "Wit.ai Node.js SDK", "keywords": [ "wit", @@ -9,7 +9,8 @@ "botengine", "bots", "nlp", - "automation" + "automation", + "composer" ], "main": "index.js", "scripts": {