From 851a1747dadc24e2dde4c7a94215dfd6397126a2 Mon Sep 17 00:00:00 2001 From: Evgeniy Semin Date: Fri, 28 Jul 2023 16:16:05 +0300 Subject: [PATCH] =?UTF-8?q?test:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D1=82=D0=B5=D1=81=D1=82=D1=8B=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=BF=D0=BE=D0=B4=D0=BF=D0=B8=D1=81=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cypress/integration/autolistening.spec.ts | 354 ++++++++--------- cypress/integration/disableDubbing.spec.ts | 36 +- cypress/integration/listen.spec.ts | 4 +- cypress/integration/subscriptions.spec.ts | 376 ++++++++++++++++++ .../support/helpers/clientMethods.helpers.ts | 8 +- src/assistantSdk/assistant.ts | 37 +- src/assistantSdk/client/protocol.ts | 4 +- src/dev.ts | 6 +- 8 files changed, 607 insertions(+), 218 deletions(-) create mode 100644 cypress/integration/subscriptions.spec.ts diff --git a/cypress/integration/autolistening.spec.ts b/cypress/integration/autolistening.spec.ts index d8deb4918d..8f1d94b65a 100644 --- a/cypress/integration/autolistening.spec.ts +++ b/cypress/integration/autolistening.spec.ts @@ -3,7 +3,7 @@ import { Server } from 'mock-socket'; import { Message } from '../../src/proto'; -import { createAssistantClient, MessageNames, EmotionId } from '../../src'; +import { createAssistantClient, MessageNames } from '../../src'; import { createMessage, createVoiceMessage } from '../support/helpers/clientMethods.helpers'; import { initServer, initAssistantClient } from '../support/helpers/init'; @@ -17,183 +17,181 @@ describe('Автослушание', () => { }); afterEach(() => { - server?.stop(); + server?.stop(); }); - it('Озвучивание прекращается при установке disableDubbing=true', (done) => { - let currentEmotion: EmotionId = 'idle'; - - server.on('connection', (socket) => { - socket.on('message', (data) => { - const message = Message.decode((data as Uint8Array).slice(4)); - const { messageId, text } = message; - - if (text?.data === 'hello') { - [1,2,3,4,5,6,7,8].forEach((i) => { - if (i === 4) { - assistantClient.changeSettings({ disableDubbing: true }); - } - - socket.send( - createVoiceMessage({ - messageId, - last: i === 8 ? 1 : -1, - }) - ); - - expect(currentEmotion).eq(i >= 4 ? 'idle' : 'talk', 'Остановка озвучки работает'); - - if (i === 8) { - done(); - } - }); - } - }); - }); - - assistantClient.on('assistant', ({ emotion }) => { - if (typeof emotion !== 'undefined') { - currentEmotion = emotion; - } - }); - - assistantClient.changeSettings({ disableDubbing: false }); - - assistantClient.start(); - - assistantClient.sendText('hello'); - }); - - it('Автослушание не запускается при disableListening=true', () => { - server.on('connection', (socket) => { - socket.on('message', (data) => { - const message = Message.decode((data as Uint8Array).slice(4)); - const { messageId, text, voice } = message; - - if (voice) { - throw new Error('Слушание запускаться не должно'); - } - - if (text?.data === 'hello') { - socket.send( - createMessage({ - messageId, - messageName: MessageNames.ANSWER_TO_USER, - systemMessage: { - auto_listening: true, - items: [{ bubble: { text: 'Hello!' } }], - }, - last: 1, - }) - ); - } - }); - }); - - assistantClient.changeSettings({ - disableDubbing: true, - disableListening: true, - }); - - assistantClient.start(); - - assistantClient.sendText('hello'); - - cy.wait(2000); - }); - - it('При установке disableDubbing=true после запуска слушания ответ не озвучивается, запускается автослушание', () => { - let hadListening = false; - - server.on('connection', (socket) => { - socket.on('message', (data) => { - const message = Message.decode((data as Uint8Array).slice(4)); - const { messageId, text, voice } = message; - - if (voice) { - hadListening = true; - } - - if (text?.data === 'hello') { - socket.send( - createVoiceMessage({ - messageId, - messageName: MessageNames.ANSWER_TO_USER, - systemMessage: { - auto_listening: true, - }, - last: 1, - }) - ); - } - }); - }); - - assistantClient.on('assistant', ({ emotion }) => { - if (emotion === 'talk') { - throw new Error('Озвучивание запускаться не должно'); - } - }); - - assistantClient.changeSettings({ - disableDubbing: true, - disableListening: false, - }); - - assistantClient.start(); - - assistantClient.sendText('hello'); - - // Не используем done(), чтобы в любом случае пройти обе проверки - cy.wait(2000).then(() => { - expect(hadListening).eq(true, 'Автослушание должно запуститься'); - }); - }); - - const testAutolisteningAfterVoiceAnswer = (done: () => void, disableDubbing: boolean) => { - let isTestDone = false; - - server.on('connection', (socket) => { - socket.on('message', (data) => { - const message = Message.decode((data as Uint8Array).slice(4)); - const { messageId, text, voice } = message; - - if (voice && !isTestDone) { - done(); - - isTestDone = true; - } - - if (text?.data === 'hello') { - socket.send( - createVoiceMessage({ - messageId, - messageName: MessageNames.ANSWER_TO_USER, - systemMessage: { - auto_listening: true, - }, - last: 1, - }) - ); - } - }); - }); - - assistantClient.changeSettings({ - disableDubbing, - disableListening: false, - }); - - assistantClient.start(); - - assistantClient.sendText('hello'); - }; - - it('Автослушание после голосового ответа работает (при disableDubbing=false)', (done) => { - testAutolisteningAfterVoiceAnswer(done, false); - }); - - it('Автослушание после голосового ответа работает (при disableDubbing=true)', (done) => { - testAutolisteningAfterVoiceAnswer(done, true); - }); + it('Озвучивание прекращается при установке disableDubbing=true', (done) => { + let ttsStatus: 'start' | 'stop' | null = null; + + server.on('connection', (socket) => { + socket.on('message', (data) => { + const message = Message.decode((data as Uint8Array).slice(4)); + const { messageId, text } = message; + + if (text?.data === 'hello') { + [1, 2, 3, 4, 5, 6, 7, 8].forEach((i) => { + if (i === 4) { + assistantClient.changeSettings({ disableDubbing: true }); + } + + socket.send( + createVoiceMessage({ + messageId, + last: i === 8 ? 1 : -1, + }), + ); + + expect(ttsStatus).eq(i >= 4 ? 'stop' : 'start', 'Остановка озвучки работает'); + + if (i === 8) { + done(); + } + }); + } + }); + }); + + assistantClient.on('tts', ({ status }) => { + ttsStatus = status; + }); + + assistantClient.changeSettings({ disableDubbing: false }); + + assistantClient.start(); + + assistantClient.sendText('hello'); + }); + + it('Автослушание не запускается при disableListening=true', () => { + server.on('connection', (socket) => { + socket.on('message', (data) => { + const message = Message.decode((data as Uint8Array).slice(4)); + const { messageId, text, voice } = message; + + if (voice) { + throw new Error('Слушание запускаться не должно'); + } + + if (text?.data === 'hello') { + socket.send( + createMessage({ + messageId, + messageName: MessageNames.ANSWER_TO_USER, + systemMessage: { + auto_listening: true, + items: [{ bubble: { text: 'Hello!' } }], + }, + last: 1, + }), + ); + } + }); + }); + + assistantClient.changeSettings({ + disableDubbing: true, + disableListening: true, + }); + + assistantClient.start(); + + assistantClient.sendText('hello'); + + cy.wait(2000); + }); + + it('При установке disableDubbing=true после запуска слушания ответ не озвучивается, запускается автослушание', () => { + let hadListening = false; + + server.on('connection', (socket) => { + socket.on('message', (data) => { + const message = Message.decode((data as Uint8Array).slice(4)); + const { messageId, text, voice } = message; + + if (voice) { + hadListening = true; + } + + if (text?.data === 'hello') { + socket.send( + createVoiceMessage({ + messageId, + messageName: MessageNames.ANSWER_TO_USER, + systemMessage: { + auto_listening: true, + }, + last: 1, + }), + ); + } + }); + }); + + assistantClient.on('tts', ({ status }) => { + if (status === 'start') { + throw new Error('Озвучивание запускаться не должно'); + } + }); + + assistantClient.changeSettings({ + disableDubbing: true, + disableListening: false, + }); + + assistantClient.start(); + + assistantClient.sendText('hello'); + + // Не используем done(), чтобы в любом случае пройти обе проверки + cy.wait(2000).then(() => { + expect(hadListening).eq(true, 'Автослушание должно запуститься'); + }); + }); + + const testAutolisteningAfterVoiceAnswer = (done: () => void, disableDubbing: boolean) => { + let isTestDone = false; + + server.on('connection', (socket) => { + socket.on('message', (data) => { + const message = Message.decode((data as Uint8Array).slice(4)); + const { messageId, text, voice } = message; + + if (voice && !isTestDone) { + done(); + + isTestDone = true; + } + + if (text?.data === 'hello') { + socket.send( + createVoiceMessage({ + messageId, + messageName: MessageNames.ANSWER_TO_USER, + systemMessage: { + auto_listening: true, + }, + last: 1, + }), + ); + } + }); + }); + + assistantClient.changeSettings({ + disableDubbing, + disableListening: false, + }); + + assistantClient.start(); + + assistantClient.sendText('hello'); + }; + + it('Автослушание после голосового ответа работает (при disableDubbing=false)', (done) => { + testAutolisteningAfterVoiceAnswer(done, false); + }); + + it('Автослушание после голосового ответа работает (при disableDubbing=true)', (done) => { + testAutolisteningAfterVoiceAnswer(done, true); + }); }); diff --git a/cypress/integration/disableDubbing.spec.ts b/cypress/integration/disableDubbing.spec.ts index c5fb7c4ef6..63b1e514d0 100644 --- a/cypress/integration/disableDubbing.spec.ts +++ b/cypress/integration/disableDubbing.spec.ts @@ -120,21 +120,22 @@ describe('Проверяем изменение настроек озвучки' assistantClient.start(); assistantClient.changeSettings({ disableDubbing: true, disableListening: true }); }); + it('При включенной озвучке changeSettings применит настройки при завершении озвучки', (done) => { let phase: 1 | 2 | 3 = 1; let intervalId; - assistantClient.on('assistant', (event) => { - if (event.emotion === 'talk') { + assistantClient.on('tts', ({ status }) => { + if (status === 'start') { phase = 2; // talking phase expect(assistantClient.settings.disableDubbing, 'dubbing не изменился во время озвучки').to.equal( false, ); assistantClient.changeSettings({ disableDubbing: true }); } - if (event.emotion === 'idle' && phase === 2) { - clearInterval(intervalId); + if (status === 'stop' && phase === 2) { + window.clearInterval(intervalId); phase = 3; // stopped talking phase } }); @@ -222,13 +223,13 @@ describe('Проверяем изменение настроек озвучки' it('sendText останавливает озвучку и слушание', (done) => { let intervalId; - assistantClient.on('assistant', (event) => { - if (event.emotion === 'talk') { + assistantClient.on('tts', ({ status }) => { + if (status === 'start') { assistantClient.sendText('text'); } - if (event.emotion === 'idle') { - clearInterval(intervalId); + if (status === 'stop') { + window.clearInterval(intervalId); done(); } }); @@ -251,6 +252,7 @@ describe('Проверяем изменение настроек озвучки' assistantClient.start(); assistantClient.changeSettings({ disableDubbing: false }); }); + it('sendText при shouldSendDisableDubbing: отправляет {dubbing: -1}', (done) => { let settingsReceived = false; let textSent = false; @@ -300,17 +302,18 @@ describe('Проверяем изменение настроек озвучки' assistantClient.start(); }); + it('stopVoice останавливает озвучку', (done) => { let intervalId; - assistantClient.on('assistant', (event) => { - if (event.emotion === 'talk') { + assistantClient.on('tts', ({ status }) => { + if (status === 'start') { assistantClient.stopVoice(); return; } - expect(event.emotion, 'stopVoice останавливает озвучку').to.equal('idle'); - clearInterval(intervalId); + expect(status, 'stopVoice останавливает озвучку').to.equal('stop'); + window.clearInterval(intervalId); done(); }); @@ -333,10 +336,17 @@ describe('Проверяем изменение настроек озвучки' assistantClient.start(); assistantClient.changeSettings({ disableDubbing: false }); }); + it('stopVoice останавливает слушание', (done) => { + let listenerStatus: 'listen' | 'started' | 'stopped' | null = null; + assistantClient.on('assistant', (event) => { + if (event.listener) { + listenerStatus = event.listener.status; + } + if (event.asr) { - expect(event.emotion, 'stopVoice останавливает слушание').to.equal('idle'); + expect(listenerStatus, 'stopVoice останавливает слушание').to.equal('stopped'); done(); } }); diff --git a/cypress/integration/listen.spec.ts b/cypress/integration/listen.spec.ts index 5806ef71c1..e9178029cb 100644 --- a/cypress/integration/listen.spec.ts +++ b/cypress/integration/listen.spec.ts @@ -44,8 +44,8 @@ describe('Проверяем изменение настроек озвучки' }); it('Cancel от VPS останавливает слушание', (done) => { - assistantClient.on('assistant', (event) => { - if (event.emotion === 'idle') { + assistantClient.on('assistant', ({ listener }) => { + if (listener?.status === 'stopped') { done(); } }); diff --git a/cypress/integration/subscriptions.spec.ts b/cypress/integration/subscriptions.spec.ts new file mode 100644 index 0000000000..3b95340ece --- /dev/null +++ b/cypress/integration/subscriptions.spec.ts @@ -0,0 +1,376 @@ +/* eslint-disable camelcase */ +/// + +import { ActionCommand, AppInfo } from '@salutejs/scenario'; + +import { Message } from '../../src/proto'; +import { MessageNames } from '../../src/typings'; +import { VoiceListenerStatus } from '../../src/assistantSdk/voice/listener/voiceListener'; +import { createMessage, createVoiceMessage } from '../support/helpers/clientMethods.helpers'; +import { initAssistantClient, initServer } from '../support/helpers/init'; + +describe('Подписки на события', () => { + let server: ReturnType; + let assistantClient: ReturnType; + + beforeEach(() => { + server = initServer(); + assistantClient = initAssistantClient(); + }); + + afterEach(() => { + server.stop(); + }); + + it('status', (done) => { + const status = { + code: -200, + description: 'its ok', + technicalDescription: 'its ok', + }; + + server.on('connection', (socket) => { + socket.on('message', (data) => { + const message = Message.decode((data as Uint8Array).slice(4)); + + if (message.messageName === 'OPEN_ASSISTANT') { + socket.send(createMessage({ status })); + } + }); + }); + + assistantClient.start(); + + assistantClient.on('status', (event) => { + expect(event, 'status is ok').deep.eq(status); + done(); + }); + }); + + it('assistant (asr, listener)', (done) => { + const asr = 'recognized text'; + + let prevListenerStatus: VoiceListenerStatus = 'stopped'; + let eventsCount = 0; + + server.on('connection', (socket) => { + socket.on('message', (data) => { + const message = Message.decode((data as Uint8Array).slice(4)); + + // Отправка asr + if (message.voice) { + socket.send( + createMessage({ + messageName: MessageNames.STT, + messageId: message.messageId, + text: { + data: asr, + }, + }), + ); + } + }); + }); + + assistantClient.on('assistant', (event) => { + if (prevListenerStatus === 'listen' && event.listener?.status === 'stopped') { + eventsCount += 1; + expect(true, 'listener is ok').eq(true); + } + + if (event.listener) { + prevListenerStatus = event.listener.status; + } + + if (event.asr?.text) { + eventsCount += 1; + expect(event.asr.text, 'asr is ok').eq(asr); + } + + if (eventsCount === 2) { + done(); + } + }); + + assistantClient.start(); + assistantClient.listen(); + }); + + it('tts (status)', (done) => { + let lastTtsStatus: 'start' | 'stop' | null = null; + + server.on('connection', (socket) => { + socket.on('message', (data) => { + const message = Message.decode((data as Uint8Array).slice(4)); + + if (message.messageName === 'OPEN_ASSISTANT') { + socket.send(createVoiceMessage()); + } + }); + }); + + assistantClient.on('tts', ({ status }) => { + if (status === 'stop' && lastTtsStatus === 'start') { + done(); + } + + lastTtsStatus = status; + }); + + assistantClient.changeSettings({ disableDubbing: false }); + assistantClient.start(); + }); + + it('vps (ready, incoming, outcoming)', (done) => { + const text = 'lorem'; + + let eventsCount = 0; + + server.on('connection', (socket) => { + socket.on('message', (data) => { + const message = Message.decode((data as Uint8Array).slice(4)); + + if (message.messageName === 'OPEN_ASSISTANT') { + socket.send(createMessage({ text: { data: text } })); + } + }); + }); + + assistantClient.on('vps', (event) => { + if (event.type === 'outcoming' && event.message.text?.data === text) { + eventsCount += 1; + expect(true, 'outcoming is ok').eq(true); + } + + if (event.type === 'incoming') { + eventsCount += 1; + expect(event.originalMessage.text?.data, 'incoming is ok').eq(text); + } + + if (event.type === 'ready') { + eventsCount += 1; + expect(true, 'ready is ok').eq(true); + } + + if (eventsCount === 3) { + done(); + } + }); + + assistantClient.start(); + assistantClient.sendText(text); + }); + + it('actionCommand', (done) => { + const actionCommand: ActionCommand = { + type: 'action', + action: { + type: 'text', + text: 'message', + }, + }; + + server.on('connection', (socket) => { + socket.on('message', (data) => { + const message = Message.decode((data as Uint8Array).slice(4)); + + if (message.messageName === 'OPEN_ASSISTANT') { + socket.send( + createMessage({ + systemMessage: { + items: [ + { + command: actionCommand, + }, + ], + }, + }), + ); + } + }); + }); + + assistantClient.on('actionCommand', (event) => { + expect(event.command, 'actionCommand is ok').deep.eq(actionCommand); + done(); + }); + + assistantClient.start(); + }); + + it('app (run, command, close)', (done) => { + const appInfo: AppInfo = { + projectId: 'my-app', + applicationId: 'my-app-applicationId', + appversionId: 'my-app-appversionId', + frontendType: 'WEB_APP', + }; + + const smartAppData = { + type: 'smart_app_data', + smart_app_data: { data: 'my_data' }, + }; + + const smartAppError = { + type: 'smart_app_error', + smart_app_error: { data: 'my_error' }, + }; + + let sendToAssistant: (data: string | Blob | ArrayBuffer | ArrayBufferView) => void; + + server.on('connection', (socket) => { + sendToAssistant = (data) => socket.send(data); + + socket.on('message', (data) => { + const message = Message.decode((data as Uint8Array).slice(4)); + + if (message.messageName === 'OPEN_ASSISTANT') { + socket.send( + createMessage({ + systemMessage: { + activate_app_info: true, + app_info: appInfo, + }, + }), + ); + } + }); + }); + + assistantClient.on('app', (event) => { + if (event.type === 'run') { + expect(event.app, 'run is ok').deep.eq(appInfo); + assistantClient.setActiveApp(appInfo); + sendToAssistant( + createMessage({ + systemMessage: { + app_info: appInfo, + items: [ + { + command: smartAppData, + }, + { + command: smartAppError, + }, + ], + }, + }), + ); + } + + if (event.type === 'command' && event.command.type === 'smart_app_data') { + delete event.command.sdk_meta; + + expect(event.app, 'command.app is ok').deep.eq(appInfo); + expect(event.command, 'command.data is ok').deep.eq(smartAppData); + } + + if (event.type === 'command' && event.command.type === 'smart_app_error') { + delete event.command.sdk_meta; + + expect(event.app, 'command.app is ok').deep.eq(appInfo); + expect(event.command, 'command.error is ok').deep.eq(smartAppError); + assistantClient.closeApp(); + } + + if (event.type === 'close') { + expect(event.app, 'close is ok').deep.eq(appInfo); + done(); + } + }); + + assistantClient.start(); + }); + + it('app (run): WEB_APP не откроется дважды, а DIALOG откроется', (done) => { + const webApp: AppInfo = { + projectId: 'my-app', + applicationId: 'my-app-applicationId', + appversionId: 'my-app-appversionId', + frontendType: 'WEB_APP', + }; + + const dialog: AppInfo = { + projectId: 'my-app', + applicationId: 'my-app-applicationId', + appversionId: 'my-app-appversionId', + frontendType: 'DIALOG', + }; + + const openDialog = 'open DIALOG'; + const openWebApp = 'open WEB_APP'; + + server.on('connection', (socket) => { + socket.on('message', (data) => { + const message = Message.decode((data as Uint8Array).slice(4)); + + if (message.text?.data) { + socket.send( + createMessage({ + systemMessage: { + activate_app_info: true, + app_info: message.text?.data === openDialog ? dialog : webApp, + }, + }), + ); + } + }); + }); + + assistantClient.on('app', (event) => { + if (event.type === 'run' && event.app.frontendType === 'DIALOG') { + expect(event.app, 'open DIALOG is ok').deep.eq(dialog); + assistantClient.setActiveApp(webApp); + assistantClient.sendText(openWebApp); + } + + if ( + event.type === 'run' && + event.app.frontendType === 'WEB_APP' && + assistantClient.activeApp?.frontendType === 'WEB_APP' + ) { + throw new Error('RUN_APP не должен прихолить для открытого WEB_APP'); + } + }); + + assistantClient.start(); + assistantClient.setActiveApp(dialog); + assistantClient.sendText(openDialog); + + cy.wait(1000).then(done); + }); + + it('command', (done) => { + const command = { + type: 'smart_app_data', + smart_app_data: { data: 'my_data' }, + }; + + server.on('connection', (socket) => { + socket.on('message', (data) => { + const message = Message.decode((data as Uint8Array).slice(4)); + + if (message.messageName === 'OPEN_ASSISTANT') { + socket.send( + createMessage({ + systemMessage: { + items: [ + { + command, + }, + ], + }, + }), + ); + } + }); + }); + + assistantClient.on('command', (event) => { + expect(event, 'command is ok').deep.eq(command); + done(); + }); + + assistantClient.start(); + }); +}); diff --git a/cypress/support/helpers/clientMethods.helpers.ts b/cypress/support/helpers/clientMethods.helpers.ts index 1ba56ae07d..6af59d954e 100644 --- a/cypress/support/helpers/clientMethods.helpers.ts +++ b/cypress/support/helpers/clientMethods.helpers.ts @@ -1,7 +1,7 @@ import { Server } from 'mock-socket'; import { appendHeader } from '../../../src/assistantSdk/client/protocol'; -import { Message, IText } from '../../../src/proto'; +import { Message, IText, IStatus, IBytes } from '../../../src/proto'; import { SystemMessageDataType, MessageNames, Character } from '../../../src/typings'; type CreateAnswerBuffer1Params = { @@ -10,7 +10,9 @@ type CreateAnswerBuffer1Params = { systemMessage?: Partial & { character: Partial }>; messageName?: string; text?: IText; + status?: IStatus; appendVoice?: boolean; + bytes?: IBytes; }; export const createMessage = ({ @@ -19,7 +21,9 @@ export const createMessage = ({ systemMessage, messageName = MessageNames.ANSWER_TO_USER, text, + status, appendVoice = false, + bytes, }: CreateAnswerBuffer1Params) => { const encodedAsNodeBuffer = appendHeader( Message.encode({ @@ -34,7 +38,9 @@ export const createMessage = ({ data: Uint8Array.from({ length: 100 }, () => Math.floor(Math.random() * 5)), }, text, + status, last, + bytes, }).finish(), ); const newBuffer = new ArrayBuffer(encodedAsNodeBuffer.byteLength); diff --git a/src/assistantSdk/assistant.ts b/src/assistantSdk/assistant.ts index 6df3d0f00a..c9dcc00f18 100644 --- a/src/assistantSdk/assistant.ts +++ b/src/assistantSdk/assistant.ts @@ -7,13 +7,13 @@ import { AppInfo, AssistantAppState, AssistantSmartAppData, + AssistantSmartAppError, AssistantStartSmartSearch, VpsConfiguration, EmotionId, OriginalMessageType, PermissionType, SystemMessageDataType, - CharacterId, AssistantBackgroundApp, AssistantBackgroundAppInfo, AssistantMeta, @@ -28,6 +28,7 @@ import { createProtocol, ProtocolError } from './client/protocol'; import { createTransport, CreateTransportParams } from './client/transport'; import { getAnswerForRequestPermissions, getTime } from './meta'; import { createVoice, TtsEvent } from './voice/voice'; +import { VoiceListenerStatus } from './voice/listener/voiceListener'; import { createMutexedObject } from './mutexedObject'; import { createMutexSwitcher } from './mutexSwitcher'; import { MetaStringified } from './client/methods'; @@ -48,6 +49,8 @@ const DEFAULT_APP: AppInfo = { frontendEndpoint: 'None', }; +const BASIC_SMART_APP_COMMANDS_TYPES = ['smart_app_data', 'smart_app_error', 'start_smart_search', 'navigation']; + function convertFieldValuesToString< Obj extends Record, ObjStringified = { [key in keyof Obj]: string }, @@ -81,12 +84,19 @@ const promiseTimeout = (promise: Promise, timeout: number): Promise => export type AppEvent = | { type: 'run'; app: AppInfo } | { type: 'close'; app: AppInfo } - | { type: 'command'; app: AppInfo; command: AssistantSmartAppData | AssistantStartSmartSearch }; + | { + type: 'command'; + app: AppInfo; + command: AssistantSmartAppData | AssistantSmartAppError | AssistantStartSmartSearch; + }; export type AssistantEvent = { asr?: { text: string; last?: boolean; mid?: OriginalMessageType['messageId'] }; // last и mid нужен для отправки исх бабла в чат - character?: CharacterId; + /** + * @deprecated Use the `on('assistant', { listener })` and `on('tts', tts)` subscriptions to receive voice events + */ emotion?: EmotionId; + listener?: { status: VoiceListenerStatus }; }; export type VpsEvent = @@ -120,7 +130,7 @@ export interface CreateAssistantDevOptions { } type BackgroundAppOnCommand = ( - command: (AssistantSmartAppData & { smart_app_data?: T }) | AssistantStartSmartSearch, + command: (AssistantSmartAppData & { smart_app_data?: T }) | AssistantSmartAppError | AssistantStartSmartSearch, messageId: string, ) => void; @@ -363,19 +373,14 @@ export const createAssistant = ({ subscriptions.push( client.on('systemMessage', (systemMessage: SystemMessageDataType, originalMessage: OriginalMessageType) => { if (originalMessage.messageName === 'ANSWER_TO_USER') { - const { activate_app_info, items, app_info: mesAppInfo, character } = systemMessage; - - if (character) { - emit('assistant', { character: character.id }); - } + const { activate_app_info, items, app_info: mesAppInfo } = systemMessage; // по-умолчанию activate_app_info: true if ( activate_app_info !== false && mesAppInfo && // игнорируем activate_app_info для чатапов - (mesAppInfo?.frontendType === 'DIALOG' || - mesAppInfo?.frontendType === 'CHAT_APP' || + (['DIALOG', 'CHAT_APP'].includes(mesAppInfo.frontendType) || mesAppInfo.applicationId !== app.info.applicationId) ) { emit('app', { type: 'run', app: mesAppInfo }); @@ -386,7 +391,7 @@ export const createAssistant = ({ const { command } = items[i]; if (typeof command !== 'undefined') { - setTimeout(() => emit('command', command)); + window.setTimeout(() => emit('command', command)); if (command.type === 'start_music_recognition') { voice.shazam(); @@ -409,13 +414,7 @@ export const createAssistant = ({ }); } - if ( - (command.type === 'smart_app_data' || - command.type === 'smart_app_error' || - command.type === 'start_smart_search' || - command.type === 'navigation') && - mesAppInfo - ) { + if (mesAppInfo && BASIC_SMART_APP_COMMANDS_TYPES.includes(command.type)) { // эмитим все команды, т.к бывают фоновые команды emit('app', { type: 'command', diff --git a/src/assistantSdk/client/protocol.ts b/src/assistantSdk/client/protocol.ts index e2c31b86a3..0eea4494c5 100644 --- a/src/assistantSdk/client/protocol.ts +++ b/src/assistantSdk/client/protocol.ts @@ -170,7 +170,7 @@ export const createProtocol = ( return sendInitialSettingsOriginal(data, ...args); }) as typeof sendInitialSettingsOriginal; - const getHistoryRequest = (data: IChatHistoryRequest & { history?: GetHistoryRequestClient }) => { + const getHistoryRequest = (data: IChatHistoryRequest & { history?: GetHistoryRequestClient } = {}) => { return getHistoryRequestOriginal({ device: currentSettings.device || null, uuid: { @@ -275,7 +275,7 @@ export const createProtocol = ( status = 'connected'; - clearTimeout(clearReadyTimer); + window.clearTimeout(clearReadyTimer); /// считаем коннект = ready, если по истечении таймаута сокет не был разорван /// т.к бек может разрывать сокет, если с settings что-то не так diff --git a/src/dev.ts b/src/dev.ts index 0e70581fdd..706ea04567 100644 --- a/src/dev.ts +++ b/src/dev.ts @@ -261,9 +261,9 @@ export const initializeNativeSDKEmulator = ({ }; const subscribeToListenerStatus = (cb: (event: 'listen' | 'stopped') => void): (() => void) => - assistant.on('assistant', (event) => { - if (event.emotion) { - cb(event.emotion === 'listen' ? 'listen' : 'stopped'); + assistant.on('assistant', ({ listener }) => { + if (listener) { + cb(listener.status === 'stopped' ? 'stopped' : 'listen'); } }); const subscribeToListenerHypotesis = (cb: (hypotesis: string, last: boolean) => void): (() => void) =>