import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import crypto from 'crypto'; import { logInfo, logError, logDebug } from '../logger/index.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const HISTORY_DIR = path.join(__dirname, '..', '..', 'session', 'history'); const MAX_HISTORY_LENGTH = 100; export function initHistoryDirectory() { if (!fs.existsSync(HISTORY_DIR)) { fs.mkdirSync(HISTORY_DIR, { recursive: true }); logInfo(`Создана директория для истории чатов: ${HISTORY_DIR}`); } } export function generateChatId() { return crypto.randomUUID(); } export function createChat(chatName) { const chatId = generateChatId(); const chatInfo = { id: chatId, name: chatName || `Новый чат ${new Date().toLocaleString()}`, created: Date.now(), messages: [] }; saveHistory(chatId, chatInfo); logInfo(`Создан новый чат [${chatId}] с именем "${chatInfo.name}"`); return chatId; } function getHistoryFilePath(chatId) { return path.join(HISTORY_DIR, `${chatId}.json`); } export function saveHistory(chatId, data) { try { initHistoryDirectory(); const historyFilePath = getHistoryFilePath(chatId); fs.writeFileSync(historyFilePath, JSON.stringify(data, null, 2), 'utf8'); logDebug(`История чата ${chatId} успешно сохранена`); return true; } catch (error) { logError(`Ошибка при сохранении истории чата ${chatId}`, error); return false; } } export function loadHistory(chatId) { try { const historyFilePath = getHistoryFilePath(chatId); if (fs.existsSync(historyFilePath)) { const rawData = fs.readFileSync(historyFilePath, 'utf8'); logDebug(`Данные чата ${chatId} успешно загружены`); let data; try { data = JSON.parse(rawData); logDebug(`Данные чата ${chatId} успешно распарсены`); } catch (parseErr) { logError(`Ошибка при парсинге данных чата ${chatId}`, parseErr); return { id: chatId, name: `Восстановленный чат ${new Date().toLocaleString()}`, created: Date.now(), messages: [] }; } // Поддержка обратной совместимости со старым форматом if (Array.isArray(data)) { logDebug(`Чат ${chatId} использует устаревший формат, выполняется конвертация`); return { id: chatId, name: `Чат от ${new Date().toLocaleString()}`, created: Date.now(), messages: data, wasConverted: true }; } // Проверяем наличие обязательных полей if (!data.messages) { logInfo(`Чат ${chatId} не содержит сообщений, инициализируем пустой массив`); data.messages = []; } if (!data.name) { data.name = `Чат ${chatId.substring(0, 6)}`; } if (!data.created) { data.created = Date.now(); } if (!data.id) { data.id = chatId; } return data; } else { logInfo(`Файл истории для чата ${chatId} не найден`); } } catch (error) { logError(`Ошибка при загрузке истории чата ${chatId}`, error); } // Если не удалось загрузить, создаем новые данные logInfo(`Создаем новую историю для чата ${chatId}`); return { id: chatId, name: `Новый чат ${new Date().toLocaleString()}`, created: Date.now(), messages: [] }; } export function chatExists(chatId) { const historyFilePath = getHistoryFilePath(chatId); const exists = fs.existsSync(historyFilePath); logDebug(`Проверка существования чата ${chatId}: ${exists ? 'найден' : 'не найден'}`); return exists; } export function renameChat(chatId, newName) { try { if (!chatExists(chatId)) { logError(`Попытка переименовать несуществующий чат ${chatId}`); return false; } const chatData = loadHistory(chatId); const oldName = chatData.name; chatData.name = newName; const success = saveHistory(chatId, chatData); if (success) { logInfo(`Чат ${chatId} переименован: "${oldName}" -> "${newName}"`); } else { logError(`Не удалось переименовать чат ${chatId}`); } return success; } catch (error) { logError(`Ошибка при переименовании чата ${chatId}`, error); return false; } } export function addUserMessage(chatId, content) { const timestamp = Math.floor(Date.now() / 1000); const messageId = crypto.randomUUID(); // Определяем тип содержимого и его длину для логирования let contentDesc; if (Array.isArray(content)) { // Составное сообщение (текст + изображения) const textParts = content.filter(item => item.type === 'text'); const imageParts = content.filter(item => item.type === 'image'); const fileParts = content.filter(item => item.type === 'file'); contentDesc = `составное сообщение (${textParts.length} текст., ${imageParts.length} изобр., ${fileParts.length} файл.)`; } else if (typeof content === 'object' && content !== null) { contentDesc = 'объект-сообщение'; } else { contentDesc = `текст длиной ${String(content).length}`; } const message = { id: messageId, role: "user", content: content, timestamp: timestamp, chat_type: "t2t" }; logInfo(`Добавление сообщения пользователя в чат ${chatId}: ${contentDesc}`); return addMessageToHistory(chatId, message); } export function addAssistantMessage(chatId, content, info = {}) { const timestamp = Math.floor(Date.now() / 1000); const messageId = crypto.randomUUID(); const message = { id: messageId, role: "assistant", content: content, timestamp: timestamp, info: info, chat_type: "t2t" }; logInfo(`Добавление ответа ассистента в чат ${chatId}, длина: ${content.length}`); return addMessageToHistory(chatId, message); } function addMessageToHistory(chatId, message) { try { let chatData = loadHistory(chatId); if (chatData.messages.length >= MAX_HISTORY_LENGTH) { logInfo(`Чат ${chatId} достиг максимальной длины (${MAX_HISTORY_LENGTH}), удаляем старые сообщения`); chatData.messages = [chatData.messages[0], ...chatData.messages.slice(chatData.messages.length - MAX_HISTORY_LENGTH + 2)]; } chatData.messages.push(message); saveHistory(chatId, chatData); logDebug(`Сообщение ${message.id} успешно добавлено в чат ${chatId}`); return message.id; } catch (error) { logError(`Ошибка при добавлении сообщения в историю чата ${chatId}`, error); return null; } } export function getAllChats() { try { initHistoryDirectory(); const files = fs.readdirSync(HISTORY_DIR); logDebug(`Получен список файлов чатов: ${files.length} файлов`); let convertedCount = 0; const chats = files .filter(file => file.endsWith('.json')) .map(file => { const chatId = file.replace('.json', ''); const chatData = loadHistory(chatId); if (chatData.wasConverted) { convertedCount++; } return { id: chatId, name: chatData.name || `Чат ${chatId.substring(0, 6)}`, created: chatData.created || 0, messageCount: chatData.messages ? chatData.messages.length : 0, userMessageCount: chatData.messages ? chatData.messages.filter(m => m.role === 'user').length : 0 }; }); if (convertedCount > 0) { logInfo(`Конвертировано ${convertedCount} чатов из устаревшего формата`); } logInfo(`Обработано ${chats.length} чатов`); return chats.sort((a, b) => b.created - a.created); } catch (error) { logError('Ошибка при получении списка чатов', error); return []; } } export function deleteChat(chatId) { try { const historyFilePath = getHistoryFilePath(chatId); if (fs.existsSync(historyFilePath)) { fs.unlinkSync(historyFilePath); logInfo(`Чат ${chatId} успешно удален`); return true; } else { logError(`Попытка удаления несуществующего чата ${chatId}`); } } catch (error) { logError(`Ошибка при удалении чата ${chatId}`, error); } return false; } export function deleteChatsAutomatically(criteria = {}) { try { const { olderThan, userMessageCountLessThan, messageCountLessThan, maxChats } = criteria; logInfo(`Автоудаление чатов с критериями: ${JSON.stringify(criteria)}`); const chats = getAllChats(); logInfo(`Найдено ${chats.length} чатов для проверки`); let chatsToDelete = [...chats]; // Фильтрация по возрасту (в миллисекундах) if (olderThan) { const cutoffTime = Date.now() - olderThan; const oldChatsCount = chatsToDelete.filter(chat => chat.created < cutoffTime).length; logInfo(`Чатов старше ${olderThan}мс (${new Date(cutoffTime).toLocaleString()}): ${oldChatsCount}`); chatsToDelete = chatsToDelete.filter(chat => chat.created < cutoffTime); } if (userMessageCountLessThan !== undefined) { const lowUserMsgChatsCount = chatsToDelete.filter(chat => chat.userMessageCount < userMessageCountLessThan).length; logInfo(`Чатов с менее чем ${userMessageCountLessThan} сообщений пользователя: ${lowUserMsgChatsCount}`); chatsToDelete = chatsToDelete.filter(chat => chat.userMessageCount < userMessageCountLessThan); } if (messageCountLessThan !== undefined) { const lowMsgChatsCount = chatsToDelete.filter(chat => chat.messageCount < messageCountLessThan).length; logInfo(`Чатов с менее чем ${messageCountLessThan} сообщений всего: ${lowMsgChatsCount}`); chatsToDelete = chatsToDelete.filter(chat => chat.messageCount < messageCountLessThan); } if (maxChats && chats.length > maxChats) { logInfo(`Общее количество чатов (${chats.length}) превышает лимит (${maxChats}), удаляем старые чаты`); const sortedChats = [...chats].sort((a, b) => a.created - b.created); const oldestChats = sortedChats.slice(0, chats.length - maxChats); oldestChats.forEach(chat => { if (!chatsToDelete.some(c => c.id === chat.id)) { chatsToDelete.push(chat); } }); } // Удаление выбранных чатов const deletedChats = []; logInfo(`Найдено ${chatsToDelete.length} чатов для удаления`); for (const chat of chatsToDelete) { if (deleteChat(chat.id)) { deletedChats.push(chat.id); } } logInfo(`Удалено ${deletedChats.length} чатов`); return { success: true, deletedCount: deletedChats.length, deletedChats }; } catch (error) { logError('Ошибка при автоматическом удалении чатов', error); return { success: false, error: error.message }; } }