ваше сообщение коммита
This commit is contained in:
@@ -319,6 +319,101 @@ class UniversalGuestService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Извлечь имя гостя из текста сообщения через ИИ и сохранить в metadata
|
||||
* @param {string} identifier - Идентификатор гостя
|
||||
* @param {string} content - Текст сообщения
|
||||
* @param {string} channel - Канал
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async extractAndSaveGuestName(identifier, content, channel) {
|
||||
try {
|
||||
if (!content || !content.trim()) {
|
||||
return; // Нет текста для анализа
|
||||
}
|
||||
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
// Находим первое сообщение гостя
|
||||
const firstMessageResult = await db.getQuery()(
|
||||
`WITH decrypted_guest AS (
|
||||
SELECT
|
||||
id,
|
||||
decrypt_text(identifier_encrypted, $2) as guest_identifier,
|
||||
channel,
|
||||
metadata
|
||||
FROM unified_guest_messages
|
||||
WHERE user_id IS NULL
|
||||
)
|
||||
SELECT
|
||||
MIN(id) as first_message_id,
|
||||
MIN(metadata) as metadata
|
||||
FROM decrypted_guest
|
||||
WHERE guest_identifier = $1 AND channel = $3
|
||||
GROUP BY guest_identifier, channel`,
|
||||
[identifier, encryptionKey, channel]
|
||||
);
|
||||
|
||||
if (firstMessageResult.rows.length === 0) {
|
||||
return; // Гость не найден
|
||||
}
|
||||
|
||||
const firstMessage = firstMessageResult.rows[0];
|
||||
let metadata = firstMessage.metadata || {};
|
||||
|
||||
// Если metadata - строка, парсим её
|
||||
if (typeof metadata === 'string') {
|
||||
try {
|
||||
metadata = JSON.parse(metadata);
|
||||
} catch (e) {
|
||||
metadata = {};
|
||||
}
|
||||
}
|
||||
|
||||
// Если уже есть кастомное имя, не извлекаем заново
|
||||
if (metadata.custom_name) {
|
||||
logger.info(`[UniversalGuestService] У гостя ${identifier} уже есть кастомное имя: ${metadata.custom_name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Используем существующий сервис для извлечения имени
|
||||
const profileAnalysisService = require('./profileAnalysisService');
|
||||
const nameResult = await profileAnalysisService.extractName(content);
|
||||
|
||||
// Проверяем результат извлечения имени
|
||||
if (!nameResult || !nameResult.name || !nameResult.should_update_name) {
|
||||
logger.info(`[UniversalGuestService] Имя не найдено в сообщении гостя ${identifier} (confidence: ${nameResult?.confidence || 0})`);
|
||||
return;
|
||||
}
|
||||
|
||||
const extractedName = nameResult.name;
|
||||
|
||||
// Разбиваем имя на части
|
||||
const nameParts = extractedName.split(' ');
|
||||
const firstName = nameParts[0] || '';
|
||||
const lastName = nameParts.slice(1).join(' ') || '';
|
||||
|
||||
// Сохраняем имя в metadata
|
||||
metadata.custom_name = extractedName;
|
||||
metadata.custom_first_name = firstName;
|
||||
metadata.custom_last_name = lastName;
|
||||
|
||||
// Обновляем metadata первого сообщения гостя
|
||||
await db.getQuery()(
|
||||
`UPDATE unified_guest_messages
|
||||
SET metadata = $1
|
||||
WHERE id = $2`,
|
||||
[JSON.stringify(metadata), firstMessage.first_message_id]
|
||||
);
|
||||
|
||||
logger.info(`[UniversalGuestService] Имя гостя ${identifier} извлечено и сохранено: ${extractedName}`);
|
||||
|
||||
} catch (error) {
|
||||
logger.error('[UniversalGuestService] Ошибка извлечения имени гостя:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработать сообщение гостя (сохранить + получить AI ответ)
|
||||
* @param {Object} messageData
|
||||
@@ -398,6 +493,12 @@ class UniversalGuestService {
|
||||
const saveResult = await this.saveMessage(messageData);
|
||||
const processedContent = saveResult.processedContent;
|
||||
|
||||
// 1.5. Извлекаем имя из текста сообщения через ИИ (если это первое сообщение гостя)
|
||||
await this.extractAndSaveGuestName(identifier, content, channel).catch(error => {
|
||||
// Не критично, если не удалось извлечь имя - просто логируем
|
||||
logger.warn(`[UniversalGuestService] Ошибка извлечения имени гостя:`, error);
|
||||
});
|
||||
|
||||
// 2. Загружаем историю для контекста (заново, так как могли добавиться сообщения)
|
||||
const conversationHistory = await this.getHistory(identifier);
|
||||
|
||||
|
||||
@@ -70,16 +70,24 @@ async function saveBotSettings(botType, settings) {
|
||||
throw new Error(`Unknown bot type: ${botType}`);
|
||||
}
|
||||
|
||||
// Простое сохранение - детали зависят от структуры таблицы
|
||||
const { rows } = await db.getQuery()(
|
||||
`INSERT INTO ${tableName} (settings, updated_at)
|
||||
VALUES ($1, NOW())
|
||||
ON CONFLICT (id) DO UPDATE SET settings = $1, updated_at = NOW()
|
||||
RETURNING *`,
|
||||
[JSON.stringify(settings)]
|
||||
);
|
||||
const dataToSave = {
|
||||
...settings,
|
||||
updated_at: new Date()
|
||||
};
|
||||
|
||||
return rows[0];
|
||||
// Проверяем, существуют ли записи в таблице
|
||||
const existing = await encryptedDb.getData(tableName, {}, 1);
|
||||
|
||||
if (existing.length > 0) {
|
||||
// Обновляем первую запись (ожидаем, что таблица хранит единственную конфигурацию)
|
||||
return await encryptedDb.saveData(tableName, dataToSave, { id: existing[0].id });
|
||||
}
|
||||
|
||||
// Если записей нет, создаем новую
|
||||
return await encryptedDb.saveData(tableName, {
|
||||
...dataToSave,
|
||||
created_at: new Date()
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`[BotsSettings] Ошибка сохранения настроек ${botType}:`, error);
|
||||
|
||||
@@ -33,6 +33,7 @@ class EmailBot {
|
||||
this.status = 'inactive';
|
||||
this.reconnectAttempts = 0;
|
||||
this.maxReconnectAttempts = 3;
|
||||
this.periodicCheckInterval = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,7 +155,9 @@ class EmailBot {
|
||||
authTimeout: 60000,
|
||||
greetingTimeout: 30000,
|
||||
socketTimeout: 60000,
|
||||
debug: false
|
||||
debug: (info) => {
|
||||
logger.debug(`[EmailBot IMAP] ${info}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Настраиваем обработчики событий
|
||||
@@ -177,6 +180,8 @@ class EmailBot {
|
||||
logger.info('[EmailBot] IMAP соединение установлено');
|
||||
this.reconnectAttempts = 0;
|
||||
this.checkEmails();
|
||||
// Запускаем периодическую проверку новых писем каждые 5 минут
|
||||
this.startPeriodicCheck();
|
||||
});
|
||||
|
||||
this.imap.once('end', () => {
|
||||
@@ -200,6 +205,9 @@ class EmailBot {
|
||||
* Очистка IMAP соединения
|
||||
*/
|
||||
cleanupImap() {
|
||||
// Останавливаем периодическую проверку
|
||||
this.stopPeriodicCheck();
|
||||
|
||||
if (this.imap) {
|
||||
try {
|
||||
this.imap.removeAllListeners('error');
|
||||
@@ -244,22 +252,66 @@ class EmailBot {
|
||||
setTimeout(() => this.initializeImap(), reconnectDelay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Запуск периодической проверки новых писем
|
||||
*/
|
||||
startPeriodicCheck() {
|
||||
// Останавливаем предыдущий интервал, если он есть
|
||||
this.stopPeriodicCheck();
|
||||
|
||||
// Проверяем новые письма каждые 5 минут
|
||||
this.periodicCheckInterval = setInterval(() => {
|
||||
if (this.imap && this.imap.state === 'authenticated') {
|
||||
logger.info('[EmailBot] Периодическая проверка новых писем...');
|
||||
this.checkEmails();
|
||||
} else {
|
||||
logger.warn('[EmailBot] IMAP соединение не активно, пропускаем периодическую проверку');
|
||||
}
|
||||
}, 5 * 60 * 1000); // 5 минут
|
||||
|
||||
logger.info('[EmailBot] Периодическая проверка новых писем запущена (каждые 5 минут)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Остановка периодической проверки
|
||||
*/
|
||||
stopPeriodicCheck() {
|
||||
if (this.periodicCheckInterval) {
|
||||
clearInterval(this.periodicCheckInterval);
|
||||
this.periodicCheckInterval = null;
|
||||
logger.info('[EmailBot] Периодическая проверка остановлена');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка входящих писем
|
||||
*/
|
||||
checkEmails() {
|
||||
try {
|
||||
logger.info('[EmailBot] Проверка входящих писем...');
|
||||
this.imap.openBox('INBOX', false, (err, box) => {
|
||||
if (err) {
|
||||
logger.error('[EmailBot] Ошибка открытия INBOX:', err);
|
||||
return;
|
||||
}
|
||||
|
||||
this.imap.search(['ALL'], (err, results) => {
|
||||
if (err || !results || results.length === 0) {
|
||||
this.imap.end();
|
||||
logger.info(`[EmailBot] INBOX открыт. Всего сообщений: ${box.messages.total}`);
|
||||
|
||||
// Ищем только непрочитанные сообщения (UNSEEN)
|
||||
this.imap.search(['UNSEEN'], (err, results) => {
|
||||
if (err) {
|
||||
logger.error('[EmailBot] Ошибка поиска писем:', err);
|
||||
// Не закрываем соединение при ошибке поиска, оставляем его открытым для keepalive
|
||||
return;
|
||||
}
|
||||
|
||||
if (!results || results.length === 0) {
|
||||
logger.info('[EmailBot] Новых непрочитанных писем нет');
|
||||
// Не закрываем соединение, оставляем его открытым для keepalive
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(`[EmailBot] Найдено ${results.length} непрочитанных писем`);
|
||||
|
||||
const f = this.imap.fetch(results, {
|
||||
bodies: '',
|
||||
@@ -284,9 +336,11 @@ class EmailBot {
|
||||
msg.on('body', (stream, info) => {
|
||||
simpleParser(stream, async (err, parsed) => {
|
||||
if (err) {
|
||||
logger.error(`[EmailBot] Ошибка парсинга письма ${seqno}:`, err);
|
||||
processedCount++;
|
||||
if (processedCount >= totalMessages) {
|
||||
this.imap.end();
|
||||
logger.info('[EmailBot] Обработка всех писем завершена');
|
||||
// Не закрываем соединение, оставляем его открытым для keepalive
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -295,29 +349,44 @@ class EmailBot {
|
||||
messageId = parsed.messageId;
|
||||
}
|
||||
|
||||
const fromEmail = parsed.from?.value?.[0]?.address;
|
||||
logger.info(`[EmailBot] Обработка письма ${seqno} от ${fromEmail || 'неизвестного отправителя'}`);
|
||||
|
||||
const messageData = await this.extractMessageData(parsed, messageId, uid);
|
||||
if (messageData && this.messageProcessor) {
|
||||
// Обрабатываем сообщение через унифицированный процессор
|
||||
// Системное сообщение о согласиях будет добавлено к ответу ИИ внутри процессора
|
||||
const result = await this.messageProcessor(messageData);
|
||||
|
||||
// Если есть ответ ИИ с информацией о согласиях, отправляем email
|
||||
if (result && result.success && result.aiResponse) {
|
||||
const fromEmail = parsed.from?.value?.[0]?.address;
|
||||
if (fromEmail) {
|
||||
// Ответ ИИ уже содержит системное сообщение о согласиях (если нужно)
|
||||
await this.sendEmail(
|
||||
fromEmail,
|
||||
'Ответ на ваше сообщение',
|
||||
result.aiResponse.response
|
||||
);
|
||||
try {
|
||||
// Обрабатываем сообщение через унифицированный процессор
|
||||
// Системное сообщение о согласиях будет добавлено к ответу ИИ внутри процессора
|
||||
const result = await this.messageProcessor(messageData);
|
||||
logger.info(`[EmailBot] Письмо ${seqno} обработано успешно`);
|
||||
|
||||
// Если есть ответ ИИ с информацией о согласиях, отправляем email
|
||||
if (result && result.success && result.aiResponse) {
|
||||
if (fromEmail) {
|
||||
logger.info(`[EmailBot] Отправка ответа ИИ на ${fromEmail}`);
|
||||
// Ответ ИИ уже содержит системное сообщение о согласиях (если нужно)
|
||||
await this.sendEmail(
|
||||
fromEmail,
|
||||
'Ответ на ваше сообщение',
|
||||
result.aiResponse.response
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (processError) {
|
||||
logger.error(`[EmailBot] Ошибка обработки письма ${seqno}:`, processError);
|
||||
}
|
||||
} else {
|
||||
if (!messageData) {
|
||||
logger.warn(`[EmailBot] Письмо ${seqno} отфильтровано (системное или некорректное)`);
|
||||
} else if (!this.messageProcessor) {
|
||||
logger.warn('[EmailBot] messageProcessor не установлен, письмо не обработано');
|
||||
}
|
||||
}
|
||||
|
||||
processedCount++;
|
||||
if (processedCount >= totalMessages) {
|
||||
this.imap.end();
|
||||
logger.info('[EmailBot] Обработка всех писем завершена');
|
||||
// Не закрываем соединение, оставляем его открытым для keepalive
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -325,7 +394,7 @@ class EmailBot {
|
||||
|
||||
f.once('error', (err) => {
|
||||
logger.error('[EmailBot] Ошибка получения писем:', err);
|
||||
this.imap.end();
|
||||
// Не закрываем соединение при ошибке fetch, оставляем его открытым для keepalive
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user