| 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 |
| }; |
| } |
| } |