yandex-dialoger
Version:
Ещё одна библиотека/фреймворк для разработки навыков Алисы.
199 lines (198 loc) • 9.14 kB
JavaScript
"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;