umbot
Version:
Мультиплатформенный фреймворк для создания голосовых навыков и чат-ботов с единой бизнес-логикой. Встроенная поддержка ВКонтакте, Telegram, Viber, MAX, Яндекс Алисы, Маруси и Сбера SmartApp. Архитектура на адаптерах позволяет подключать любые другие платф
728 lines (727 loc) • 26.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Nlu = void 0;
const utils_1 = require("../../utils");
/**
* @class Nlu
* Класс для обработки естественного языка и извлечения сущностей из текста.
*
* Основные возможности:
* - Извлечение имен, дат, времени и геолокации
* - Распознавание встроенных интентов (согласие, отказ, помощь)
* - Поиск контактной информации (email, телефоны, ссылки)
* - Кэширование результатов для оптимизации производительности
*
* Платформенные ограничения:
* - Алиса: полная поддержка всех сущностей
* - Маруся: ограниченная поддержка геолокации
* - VK: базовая поддержка через регулярные выражения
* - Telegram: базовая поддержка через регулярные выражения
*
* Поддерживаемые форматы:
*
* 1. Даты и время:
* - Абсолютные даты: "1 января 2024", "01.01.2024"
* - Относительные даты: "завтра", "через неделю"
* - Время: "14:30", "два часа дня"
* - Интервалы: "с 10 до 18 часов", "в течение недели"
* - Периоды: "каждый понедельник", "по выходным"
*
* 2. Геолокация:
* - Полные адреса: "Москва, ул. Тверская, д. 1"
* - Города и регионы: "в Санкт-Петербурге", "Московская область"
* - Страны: "Россия", "в Германии"
* - Координаты: "55.7558° N, 37.6173° E"
*
* 3. Числа:
* - Целые: "42", "сто"
* - Дробные: "3,14", "половина"
* - Порядковые: "первый", "десятый"
* - Количественные: "пара", "дюжина"
*
* 4. Контактная информация:
* - Email: "user@example.com"
* - Телефоны: "+7 (999) 123-45-67", "8-800-555-35-35"
* - Ссылки: "http://localhost.ru", "www.site.ru"
*
* @example
* ```ts
* import { Nlu } from './components/nlu/Nlu';
*
* const nlu = new Nlu();
*
* // Извлечение имени
* const fio = nlu.getFio();
* if (fio.status) {
* console.log('Имя:', fio.result[0].first_name);
* console.log('Фамилия:', fio.result[0].last_name);
* }
*
* // Извлечение даты и времени
* const datetime = nlu.getDateTime();
* if (datetime.status) {
* const date = datetime.result[0];
* console.log('Дата:', date.year, date.month, date.day);
* console.log('Время:', date.hour, date.minute);
* }
*
* // Извлечение геолокации
* const geo = nlu.getGeo();
* if (geo.status) {
* const location = geo.result[0];
* console.log('Город:', location.city);
* console.log('Страна:', location.country);
* console.log('Координаты:', location.latitude, location.longitude);
* }
*
* // Проверка встроенных интентов
* if (nlu.isIntentConfirm()) {
* console.log('Пользователь согласился');
* }
* if (nlu.isIntentReject()) {
* console.log('Пользователь отказался');
* }
*
* // Поиск контактной информации
* const phones = Nlu.getPhone('Позвоните мне по номеру +7 (999) 123-45-67');
* if (phones.status) {
* console.log('Телефон:', phones.result[0]);
* }
*
* const emails = Nlu.getEMail('Мой email: user@example.com');
* if (emails.status) {
* console.log('Email:', emails.result[0]);
* }
*
* const links = Nlu.getLink('Посетите наш сайт http://localhost');
* if (links.status) {
* console.log('Ссылка:', links.result[0]);
* }
* ```
*/
class Nlu {
/**
* Массив с обработанными данными NLU.
* Содержит все сущности, извлеченные из текста.
*/
#nlu;
/**
* Кэш данных для оптимизации повторных запросов.
* Хранит результаты извлечения сущностей по их типам.
*
* @example
* ```ts
* // Пример содержимого кэша после обработки запроса
* {
* 'YANDEX.FIO': [{
* first_name: 'Иван',
* last_name: 'Иванов',
* patronymic_name: 'Петрович'
* }],
* 'YANDEX.DATETIME': [{
* year: 2024,
* month: 3,
* day: 15,
* hour: 14,
* minute: 30
* }],
* 'YANDEX.GEO': [{
* country: 'Россия',
* city: 'Москва',
* street: 'Тверская',
* house_number: '1'
* }]
* }
* ```
*/
#cachedData = new Map();
/**
* Регулярное выражение для поиска email адресов.
* Поддерживает стандартный формат email.
*
* @example
* ```ts
* // Находит адреса вида:
* // user@domain.com
* // user.name@domain.com
* // user-name@domain.com
* ```
*/
static EMAIL_REGEX = /\b[\w._+-]+@[\w._+-]+\.[a-zA-Z]{2,}\b/gi;
/**
* Регулярное выражение для поиска телефонных номеров.
* Поддерживает различные форматы записи номеров.
*
* @example
* ```ts
* // Находит номера вида:
* // +7 (999) 123-45-67
* // 8-999-123-45-67
* // 89991234567
* ```
*/
static PHONE_REGEX = /([\d\-() ]{4,}\d)|((?:\+|\d)[\d\-() ]{9,}\d)/gimu;
/**
* Регулярное выражение для поиска URL-ссылок.
* Поддерживает HTTP и HTTPS протоколы.
*
* @example
* ```ts
* // Находит ссылки вида:
* // http://localhost
* // http://localhost/path
* ```
*/
static LINK_REGEX = /((https?:\/\/)\S+\b)/gimu;
/**
* Тип сущности: ФИО.
* Используется для извлечения имен, фамилий и отчеств.
*
* @example
* ```ts
* const fio = nlu.getFio();
* if (fio.status) {
* console.log(fio.result[0].first_name); // "Иван"
* }
* ```
*/
static T_FIO = 'YANDEX.FIO';
/**
* Тип сущности: Геолокация.
* Используется для извлечения адресов, городов и других географических данных.
*
* Поддерживаемые форматы:
* 1. Полные адреса:
* - "Москва, ул. Тверская, д. 1"
* - "Россия, Санкт-Петербург, Невский проспект 2"
* 2. Города и регионы:
* - "в Москве", "Московская область"
* - "из Санкт-Петербурга в Москву"
* 3. Страны:
* - "Россия", "в Германии"
* - "из России в Китай"
* 4. Координаты:
* - "55.7558° N, 37.6173° E"
* - "55°45'20.9"N 37°37'02.2"E"
*
* @example
* ```ts
* const geo = nlu.getGeo();
* if (geo.status) {
* const location = geo.result[0];
* console.log('Страна:', location.country);
* console.log('Город:', location.city);
* console.log('Улица:', location.street);
* console.log('Номер дома:', location.house_number);
* console.log('Координаты:', location.latitude, location.longitude);
* }
* ```
*/
static T_GEO = 'YANDEX.GEO';
/**
* Тип сущности: Дата и время.
* Используется для извлечения дат, времени и временных интервалов.
*
* Поддерживаемые форматы:
* 1. Абсолютные даты:
* - "1 января 2024", "01.01.2024"
* - "в следующий вторник", "через неделю"
* 2. Время:
* - "14:30", "в два часа дня"
* - "через час", "через 30 минут"
* 3. Интервалы:
* - "с 10 до 18 часов"
* - "с понедельника по пятницу"
* 4. Периоды:
* - "каждый день в 9 утра"
* - "по выходным с 12 до 18"
*
* @example
* ```ts
* const dateTime = nlu.getDateTime();
* if (dateTime.status) {
* const date = dateTime.result[0];
*
* // Абсолютная дата
* if (date.year && date.month && date.day) {
* console.log(`Дата: ${date.day}.${date.month}.${date.year}`);
* }
*
* // Время
* if (date.hour !== undefined) {
* console.log(`Время: ${date.hour}:${date.minute || '00'}`);
* }
*
* // Относительное время
* if (date.relative) {
* console.log(`Относительно: ${date.relative}`);
* }
* }
* ```
*/
static T_DATETIME = 'YANDEX.DATETIME';
/**
* Тип сущности: Число.
* Используется для извлечения числовых значений.
*
* Поддерживаемые форматы:
* 1. Целые числа:
* - "42", "сто", "тысяча"
* - "минус десять", "-10"
* 2. Дробные числа:
* - "3,14", "пять целых две десятых"
* - "половина", "четверть"
* 3. Порядковые числительные:
* - "первый", "десятый"
* - "сотый", "тысячный"
* 4. Количественные числительные:
* - "пара", "дюжина"
* - "несколько", "много"
*
* @example
* ```ts
* const numbers = nlu.getNumber();
* if (numbers.status) {
* numbers.result.forEach(number => {
* // Проверка на целое число
* if (Number.isInteger(number)) {
* console.log(`Целое число: ${number}`);
* }
* // Проверка на дробное число
* else if (number % 1 !== 0) {
* console.log(`Дробное число: ${number}`);
* }
* });
* }
* ```
*/
static T_NUMBER = 'YANDEX.NUMBER';
/**
* Встроенный интент: Согласие.
* Используется для распознавания положительных ответов.
*
* Поддерживаемые варианты:
* - Прямое согласие: "да", "конечно", "хорошо"
* - Подтверждение: "верно", "правильно", "точно"
* - Готовность: "готов", "можно", "давай"
* - Одобрение: "отлично", "супер", "класс"
*
* @example
* ```ts
* // Проверка на согласие
* if (nlu.isIntentConfirm()) {
* console.log('Пользователь согласился');
* }
*
* // Проверка конкретной фразы
* if (nlu.isIntentConfirm('да, конечно')) {
* console.log('Явное согласие');
* }
* ```
*/
static T_INTENT_CONFIRM = 'YANDEX.CONFIRM';
/**
* Встроенный интент: Отказ.
* Используется для распознавания отрицательных ответов.
*
* Поддерживаемые варианты:
* - Прямой отказ: "нет", "не надо", "отмена"
* - Отрицание: "неверно", "неправильно"
* - Несогласие: "не хочу", "не буду"
* - Отмена: "стоп", "хватит", "прекрати"
*
* @example
* ```ts
* // Проверка на отказ
* if (nlu.isIntentReject()) {
* console.log('Пользователь отказался');
* }
*
* // Проверка конкретной фразы
* if (nlu.isIntentReject('нет, спасибо')) {
* console.log('Вежливый отказ');
* }
* ```
*/
static T_INTENT_REJECT = 'YANDEX.REJECT';
/**
* Встроенный интент: Запрос помощи.
* Используется для распознавания запросов о помощи.
*
* @example
* ```ts
* if (nlu.isIntentHelp()) {
* console.log('Пользователь запросил помощь');
* }
* ```
*/
static T_INTENT_HELP = 'YANDEX.HELP';
/**
* Встроенный интент: Повторение.
* Используется для распознавания запросов повторить последний ответ.
*
* @example
* ```ts
* if (nlu.isIntentRepeat()) {
* console.log('Пользователь просит повторить');
* }
* ```
*/
static T_INTENT_REPEAT = 'YANDEX.REPEAT';
/**
* Конструктор класса Nlu.
* Инициализирует пустой объект NLU и кэш данных.
*/
constructor() {
this.#nlu = {};
}
/**
* Возвращает объект nlu, который был обработан платформой, либо заполнен из плагина
*/
getNluValue() {
return this.#nlu;
}
/**
* Устанавливает данные NLU и очищает кэш.
*
* @param nlu - Данные NLU для обработки
* @param isClearCache - Флаг, говорящий о том, что нужно сбросить кэш
* @example
* ```ts
* nlu.setNlu({
* entities: [
* {
* type: 'YANDEX.FIO',
* value: { first_name: 'Иван', last_name: 'Иванов' }
* }
* ]
* });
* ```
*/
setNlu(nlu, isClearCache = false) {
this.#nlu = nlu;
if (isClearCache) {
this.#cachedData.clear();
}
}
/**
* Получает данные определенного типа из NLU.
* Использует кэш для оптимизации повторных запросов.
*
* @param {string} type - Тип данных для извлечения
* @returns Массив найденных сущностей или null
*/
#getData(type) {
if (this.#cachedData.has(type)) {
return this.#cachedData.get(type) || null;
}
let data = null;
if (Array.isArray(this.#nlu.entities)) {
this.#nlu.entities.forEach((entity) => {
if (entity.type !== undefined && entity.type === type) {
data ??= [];
data.push(entity.value);
}
});
}
this.#cachedData.set(type, data);
return data;
}
/**
* Получает информацию о текущем пользователе.
*
* @returns {INluThisUser | null} Информация о пользователе или null
* @example
* ```ts
* const user = nlu.getUserName();
* if (user) {
* console.log('ID пользователя:', user.user_id);
* console.log('Имя пользователя:', user.first_name);
* console.log('Фамилия пользователя:', user.last_name);
* }
* ```
*/
getUserName() {
return this.#nlu.thisUser || null;
}
/**
* Получает ФИО из текста.
*
* @returns {INluResult<INluFIO[]>} Результат поиска ФИО
* @example
* ```ts
* const fio = nlu.getFio();
* if (fio.status) {
* const person = fio.result[0];
* console.log('Имя:', person.first_name);
* console.log('Фамилия:', person.last_name);
* console.log('Отчество:', person.patronymic_name);
* }
* ```
*/
getFio() {
const fio = this.#getData(Nlu.T_FIO);
const status = !!fio;
return {
status,
result: fio,
};
}
/**
* Получает геолокацию из текста.
*
* @returns {INluResult<INluGeo[]>} Результат поиска геолокации
* @example
* ```ts
* const geo = nlu.getGeo();
* if (geo.status) {
* const location = geo.result[0];
* console.log('Страна:', location.country);
* console.log('Город:', location.city);
* console.log('Улица:', location.street);
* console.log('Номер дома:', location.house_number);
* console.log('Координаты:', location.latitude, location.longitude);
* }
* ```
*/
getGeo() {
const geo = this.#getData(Nlu.T_GEO);
const status = !!geo;
return {
status,
result: geo,
};
}
/**
* Получает дату и время из текста.
*
* @returns {INluResult<INluDateTime[]>} Результат поиска даты и времени
* @example
* ```ts
* const dateTime = nlu.getDateTime();
* if (dateTime.status) {
* const dt = dateTime.result[0];
* if (dt.year) console.log('Год:', dt.year);
* if (dt.month) console.log('Месяц:', dt.month);
* if (dt.day) console.log('День:', dt.day);
* if (dt.hour !== undefined) console.log('Час:', dt.hour);
* if (dt.minute !== undefined) console.log('Минуты:', dt.minute);
* if (dt.relative) console.log('Относительное время:', dt.relative);
* }
* ```
*/
getDateTime() {
const dateTime = this.#getData(Nlu.T_DATETIME);
const status = !!dateTime;
return {
status,
result: dateTime,
};
}
/**
* Получает числа из текста.
*
* @returns {INluResult<number[]>} Результат поиска чисел
* @example
* ```ts
* const number = nlu.getNumber();
* if (number.status) {
* const value = number.result[0];
* console.log('Число:', value);
* // Проверка типа числа
* if (Number.isInteger(value)) {
* console.log('Целое число');
* } else {
* console.log('Дробное число');
* }
* }
* ```
*/
getNumber() {
const number = this.#getData(Nlu.T_NUMBER);
const status = !!number;
return {
status,
result: number,
};
}
/**
* Проверяет наличие интента согласия в тексте.
*
* @param {string} [userCommand=''] - Текст для проверки
* @returns {boolean} true если найден интент согласия
* @example
* ```ts
* if (nlu.isIntentConfirm('да, согласен')) {
* console.log('Пользователь согласился');
* }
* ```
*/
isIntentConfirm(userCommand = '') {
const result = this.getIntent(Nlu.T_INTENT_CONFIRM) !== null;
if (!result && userCommand) {
return utils_1.Text.isSayTrue(userCommand);
}
return result;
}
/**
* Проверяет наличие интента отказа в тексте.
*
* @param {string} [userCommand=''] - Текст для проверки
* @returns {boolean} true если найден интент отказа
* @example
* ```ts
* if (nlu.isIntentReject('нет, не хочу')) {
* console.log('Пользователь отказался');
* }
* ```
*/
isIntentReject(userCommand = '') {
const result = this.getIntent(Nlu.T_INTENT_REJECT) !== null;
if (!result && userCommand) {
return utils_1.Text.isSayFalse(userCommand);
}
return result;
}
/**
* Проверяет наличие интента помощи в тексте.
*
* @returns {boolean} true если найден интент помощи
* @example
* ```ts
* if (nlu.isIntentHelp()) {
* console.log('Пользователь запросил помощь');
* }
* ```
*/
isIntentHelp() {
return this.getIntent(Nlu.T_INTENT_HELP) !== null;
}
/**
* Проверяет наличие интента повтора в тексте.
*
* @returns {boolean} true если найден интент повтора
* @example
* ```ts
* if (nlu.isIntentRepeat()) {
* console.log('Пользователь просит повторить');
* }
* ```
*/
isIntentRepeat() {
return this.getIntent(Nlu.T_INTENT_REPEAT) !== null;
}
/**
* Получает все интенты из текста.
*
* @returns {INluIntents | null} Объект с интентами или null
* @example
* ```ts
* const intents = nlu.getIntents();
* if (intents) {
* // Проверка наличия конкретного интента
* if (intents['YANDEX.CONFIRM']) {
* console.log('Найден интент согласия');
* }
* if (intents['YANDEX.HELP']) {
* console.log('Найден интент помощи');
* }
* // Получение слотов интента
* const helpIntent = intents['YANDEX.HELP'];
* if (helpIntent && helpIntent.slots) {
* console.log('Слоты интента:', helpIntent.slots);
* }
* }
* ```
*/
getIntents() {
return this.#nlu.intents || null;
}
/**
* Получает конкретный интент по имени.
*
* @param {string} intentName - Имя интента
* @returns {INluIntent | null} Интент или null
* @example
* ```ts
* const intent = nlu.getIntent('YANDEX.CONFIRM');
* if (intent) {
* console.log(intent.slots); // { ... }
* }
* ```
*/
getIntent(intentName) {
const intents = this.getIntents();
if (intents) {
return intents[intentName] || null;
}
return null;
}
/**
* Извлекает ссылки из текста.
*
* @param {string} query - Текст для поиска
* @returns {INluResult<string[]>} Результат поиска ссылок
* @example
* ```ts
* const links = Nlu.getLink('Посетите http://localhost');
* if (links.status) {
* links.result.forEach(link => {
* console.log('Найдена ссылка:', link);
* });
* }
* ```
*/
static getLink(query) {
const matches = [...query.matchAll(Nlu.LINK_REGEX)].map((m) => m[0]);
return {
status: matches.length > 0,
result: matches.length ? matches : null,
};
}
/**
* Извлекает телефонные номера из текста.
*
* @param {string} query - Текст для поиска
* @returns {INluResult<string[]>} Результат поиска телефонов
* @example
* ```ts
* const phones = Nlu.getPhone('Позвоните по номеру +7 (999) 123-45-67 или 8-800-555-35-35');
* if (phones.status) {
* phones.result.forEach(phone => {
* console.log('Найден телефон:', phone);
* });
* }
* ```
*/
static getPhone(query) {
const matches = [...query.matchAll(Nlu.PHONE_REGEX)].map((m) => m[0]);
return {
status: matches.length > 0,
result: matches.length ? matches : null,
};
}
/**
* Извлекает email адреса из текста.
*
* @param {string} query - Текст для поиска
* @returns {INluResult<string[]>} Результат поиска email
* @example
* ```ts
* const emails = Nlu.getEMail('Напишите на email@example.com или support@test.ru');
* if (emails.status) {
* emails.result.forEach(email => {
* console.log('Найден email:', email);
* });
* }
* ```
*/
static getEMail(query) {
const matches = [...query.matchAll(Nlu.EMAIL_REGEX)].map((m) => m[0]);
return {
status: matches.length > 0,
result: matches.length ? matches : null,
};
}
}
exports.Nlu = Nlu;