UNPKG

yandex-dialoger

Version:

Ещё одна библиотека/фреймворк для разработки навыков Алисы.

199 lines (198 loc) 9.14 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Dialog = void 0; const ReplyBuilder_1 = require("./ReplyBuilder"); const SceneProcessor_1 = require("./SceneProcessor"); const DialogsIntent_1 = require("./DialogsIntent"); const TransitionProcessor_1 = require("./TransitionProcessor"); const nameof_1 = require("./nameof"); const wait_1 = require("./wait"); const CancellationTokenSource_1 = require("./CancellationTokenSource"); class Dialog { constructor(Model, { scenes, whatCanYouDo: whatCanYouDoHandler, timeout }) { /** Оставим небольшой запас в 300мс */ this.TIMEOUT = 2700; this.scenes = new Map(); this.transitions = new Map(); this.endingsSceneNames = new Set(); this.handleRequest = (request) => { const timeoutHanler = this.timeoutHanler; if (this.isPingRequest(request)) { return this.handlePing(); } if (timeoutHanler) { const source = new CancellationTokenSource_1.CancellationTokenSource(); return Promise.race([ wait_1.wait(this.TIMEOUT, source.token).then(() => this.replyToResponse(request, timeoutHanler)), this.handleUserRequest(request), ]).then((response) => { source.cancel(); return response; }); } return this.handleUserRequest(request); }; this.Model = Model; this.whatCanYouDoHandler = whatCanYouDoHandler !== null && whatCanYouDoHandler !== void 0 ? whatCanYouDoHandler : (() => { }); this.timeoutHanler = timeout; for (const sceneName of Object.keys(scenes)) { const decl = scenes[sceneName]; if (this.isScene(decl)) { this.scenes.set(sceneName, new SceneProcessor_1.SceneProcessor(decl.onInput, decl.reply, decl.help, decl.unrecognized)); continue; } if (this.isTransition(decl)) { this.transitions.set(sceneName, new TransitionProcessor_1.TransitionProcessor(decl.onTransition, decl.reply)); continue; } if (this.isEnding(decl)) { this.scenes.set(sceneName, new SceneProcessor_1.SceneProcessor(() => 'Start', decl.reply)); this.endingsSceneNames.add(sceneName); continue; } throw new Error(`Элемент ${sceneName} не был распознан ни как сцена ни как переход или окончание.`); } } replyToResponse(request, handler) { const reply = new ReplyBuilder_1.ReplyBuilder(); const [sceneName, model] = this.getOrCreateSessionState(request); handler(reply, model); return reply.build(sceneName, model, false); } isPingRequest(request) { return request.request.original_utterance.includes('ping'); } handlePing() { return Promise.resolve({ response: { text: 'pong', end_session: true }, version: '1.0', }); } async handleUserRequest(request) { var _a; const command = request.request.command.toLowerCase(); const { nlu: { intents }, } = request.request; const reply = new ReplyBuilder_1.ReplyBuilder(); const [sceneName, model] = this.getOrCreateSessionState(request); /** * При начале новой сессии, если у первой сцены (или перехода) есть reply, * то отрабатываем его, а не onInput. Так не придётся стартовой * делать сцену с одним только пустым onInput (если это нам не нужно). */ const node = (_a = this.findTransition(sceneName)) !== null && _a !== void 0 ? _a : this.getScene(sceneName); if (request.session.new && node.hasReply()) { const interruptSceneName = await this.applyTransitionsUntilScene(sceneName, model, reply); return reply.build(sceneName, model, this.endingsSceneNames.has(interruptSceneName)); } const scene = this.getScene(sceneName); const input = { command, intents, request, isConfirm: intents && intents.hasOwnProperty(DialogsIntent_1.DialogsIntent.Confirm), isReject: intents && intents.hasOwnProperty(DialogsIntent_1.DialogsIntent.Reject), }; /** * Обработка запроса «Помощь» */ if ((intents && intents[DialogsIntent_1.DialogsIntent.Help]) || command === 'помощь') { scene.applyHelp(reply, model); return reply.build(sceneName, model, false); } /** * бработка запроса «Что ты умеешь» */ if ((intents && input.intents[DialogsIntent_1.DialogsIntent.WhatCanYouDo]) || command === 'что ты умеешь') { this.whatCanYouDoHandler(reply, model); scene.applyHelp(reply, model); return reply.build(sceneName, model, false); } if (intents) { /** * Обработка запроса «Повтори» и подобных */ if (input.intents[DialogsIntent_1.DialogsIntent.Repeat]) { scene.applyReply(reply, model); return reply.build(sceneName, model, false); } } const returnedSceneName = await scene.applyInput(input, model); /** * Unrecognized * * Обработка нераспознанного запроса, когда onInput возвращает undefined. * Добавляем unrecognized-ответ текущей сцены. * Состояние после onInput сохраняем, а $currentScene оставляем как был. */ if (!returnedSceneName) { scene.applyUnrecognized(reply, model); return reply.build(sceneName, model, false); } else { const interruptSceneName = await this.applyTransitionsUntilScene(returnedSceneName, model, reply); return reply.build(interruptSceneName, model, this.endingsSceneNames.has(interruptSceneName)); } } getOrCreateSessionState(request) { const sessionState = request.state && request.state.session; if (this.isNotEmptySessionState(sessionState)) { const model = sessionState.data; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment model.__proto__ = this.Model.prototype; return [sessionState.sceneName, model]; } return ['Start', new this.Model()]; } /** * Попадаем сюда после отработки функции onInput. * Здесь мы отрабатываем переходы (transition), если они есть и * reply у достигнутой таким образом цвены. */ async applyTransitionsUntilScene(currentScene, state, reply) { const nextSceneName = await this.applyTransitions(currentScene, state, reply); const scene = this.getScene(nextSceneName); scene.applyReply(reply, state); return nextSceneName; } async applyTransitions(currentScene, state, output) { const scene = this.findTransition(currentScene); if (!scene) { return currentScene; } scene.applyReply(output, state); const x = await scene.applyTransition(state); return this.applyTransitions(x, state, output); } findTransition(sceneName) { return this.transitions.get(sceneName); } getScene(sceneName) { const scene = this.scenes.get(sceneName); if (!scene) { throw new Error(`Сцена ${sceneName} не определена.`); } return scene; } isNotEmptySessionState(sessionState) { const sceneNamePropName = nameof_1.nameof('sceneName'); const dataPropName = nameof_1.nameof('data'); return sessionState && sceneNamePropName in sessionState && dataPropName in sessionState; } isScene(decl) { const scenePropName = nameof_1.nameof('onInput'); return typeof decl[scenePropName] === 'function'; } isTransition(decl) { const transitionPropName = nameof_1.nameof('onTransition'); return typeof decl[transitionPropName] === 'function'; } isEnding(decl) { const transitionPropName = nameof_1.nameof('onTransition'); const scenePropName = nameof_1.nameof('onInput'); return (typeof decl[transitionPropName] === 'undefined' && typeof decl[scenePropName] === 'undefined'); } } exports.Dialog = Dialog;