QwenAI / src /api /chatHistory.js
imseldrith's picture
Initial upload from Google Colab
9de864e verified
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
};
}
}