mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2024-12-12 09:26:33 +01:00
Merge pull request #3029 from P3il4/staging
Optimize chat manager logic
This commit is contained in:
commit
b837c482fc
@ -5461,7 +5461,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="selectChatPopupHeaderText" class="TxtLrgBoldCenter">
|
<div id="selectChatPopupHeaderText" class="TxtLrgBoldCenter">
|
||||||
<span id="ChatHistoryCharName"></span><span data-i18n="Chat History">Chat History</span>
|
<span id="ChatHistoryCharName"></span><span data-i18n="Chat History">Chat History</span>
|
||||||
<a href="https://docs.sillytavern.app/usage/core-concepts/chatfilemanagement/#chat-import" class="notes-link" target="_blank"><span class="fa-solid fa-circle-question note-link-span"></span></a>
|
<a href="https://docs.sillytavern.app/usage/core-concepts/chatfilemanagement/" class="notes-link" target="_blank"><span class="fa-solid fa-circle-question note-link-span"></span></a>
|
||||||
</div>
|
</div>
|
||||||
<div id="newChatFromManageScreenButton" class="menu_button menu_button_icon">
|
<div id="newChatFromManageScreenButton" class="menu_button menu_button_icon">
|
||||||
<i class="fa-solid fa-plus"></i>
|
<i class="fa-solid fa-plus"></i>
|
||||||
@ -5471,13 +5471,10 @@
|
|||||||
<i class="fa-solid fa-file-import"></i>
|
<i class="fa-solid fa-file-import"></i>
|
||||||
<span data-i18n="Import Chat">Import Chat</span>
|
<span data-i18n="Import Chat">Import Chat</span>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="select_chat_search" class="text_pole flex1" data-i18n="[placeholder]Search..." placeholder="Search..." autocomplete="off">
|
<input type="search" id="select_chat_search" class="text_pole flex1" data-i18n="[placeholder]Search..." placeholder="Search..." autocomplete="off">
|
||||||
<div id="select_chat_cross" class="opacity50p hoverglow fa-solid fa-circle-xmark fontsize120p" alt="Close Past Chat Popup"></div>
|
<div id="select_chat_cross" class="opacity50p hoverglow fa-solid fa-circle-xmark fontsize120p" alt="Close Past Chat Popup"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="select_chat_div"></div>
|
<div id="select_chat_div"></div>
|
||||||
<div id="load_select_chat_div">
|
|
||||||
<div class="fa-solid fa-hourglass fa-spin"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="background_template" class="template_element">
|
<div id="background_template" class="template_element">
|
||||||
@ -5550,11 +5547,11 @@
|
|||||||
<div class="select_chat_block_wrapper flex-container">
|
<div class="select_chat_block_wrapper flex-container">
|
||||||
<div class="select_chat_block wide100p flex-container" file_name="">
|
<div class="select_chat_block wide100p flex-container" file_name="">
|
||||||
<div id="select_chat_name_wrapper" class="flex-container alignitemscenter justifySpaceBetween wide100p">
|
<div id="select_chat_name_wrapper" class="flex-container alignitemscenter justifySpaceBetween wide100p">
|
||||||
<div>
|
<div class="flex-container alignItemsCenter">
|
||||||
<small class="select_chat_block_filename select_chat_block_filename_item"></small>
|
<small class="select_chat_block_filename select_chat_block_filename_item"></small>
|
||||||
<div title="Rename chat file" class="renameChatButton hoverglow opacity50p fa-solid fa-pencil" data-i18n="[title]Rename chat file"></div>
|
<div title="Rename chat file" class="renameChatButton hoverglow opacity50p fa-solid fa-pencil fa-sm" data-i18n="[title]Rename chat file"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-container gap10px">
|
<div class="flex-container gap10px alignItemsCenter">
|
||||||
<div class="select_chat_info flex-container">
|
<div class="select_chat_info flex-container">
|
||||||
<small class="chat_messages_date select_chat_block_filename_item"></small>
|
<small class="chat_messages_date select_chat_block_filename_item"></small>
|
||||||
<small class="chat_file_size select_chat_block_filename_item"></small>
|
<small class="chat_file_size select_chat_block_filename_item"></small>
|
||||||
|
138
public/script.js
138
public/script.js
@ -7026,99 +7026,15 @@ export async function displayPastChats() {
|
|||||||
$('#select_chat_div').empty();
|
$('#select_chat_div').empty();
|
||||||
$('#select_chat_search').val('').off('input');
|
$('#select_chat_search').val('').off('input');
|
||||||
|
|
||||||
const data = await (selected_group ? getGroupPastChats(selected_group) : getPastCharacterChats());
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
toastr.error(t`Could not load chat data. Try reloading the page.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const chatDetails = getCurrentChatDetails();
|
const chatDetails = getCurrentChatDetails();
|
||||||
const group = chatDetails.group;
|
|
||||||
const currentChat = chatDetails.sessionName;
|
const currentChat = chatDetails.sessionName;
|
||||||
const displayName = chatDetails.characterName;
|
const displayName = chatDetails.characterName;
|
||||||
const avatarImg = chatDetails.avatarImgURL;
|
const avatarImg = chatDetails.avatarImgURL;
|
||||||
|
|
||||||
const rawChats = await getChatsFromFiles(data, selected_group);
|
await displayChats('', currentChat, displayName, avatarImg, selected_group);
|
||||||
|
|
||||||
// Sort by last message date descending
|
|
||||||
data.sort((a, b) => sortMoments(timestampToMoment(a.last_mes), timestampToMoment(b.last_mes)));
|
|
||||||
console.log(data);
|
|
||||||
$('#load_select_chat_div').css('display', 'none');
|
|
||||||
$('#ChatHistoryCharName').text(`${displayName}'s `);
|
|
||||||
|
|
||||||
const displayChats = (searchQuery) => {
|
|
||||||
$('#select_chat_div').empty(); // Clear the current chats before appending filtered chats
|
|
||||||
|
|
||||||
const filteredData = data.filter(chat => {
|
|
||||||
const fileName = chat['file_name'];
|
|
||||||
const chatContent = rawChats[fileName];
|
|
||||||
|
|
||||||
// Make sure empty chats are displayed when there is no search query
|
|
||||||
if (Array.isArray(chatContent) && !chatContent.length && !searchQuery) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// // Uncomment this to return to old behavior (classical full-substring search).
|
|
||||||
// return chatContent && Object.values(chatContent).some(message => message?.mes?.toLowerCase()?.includes(searchQuery.toLowerCase()));
|
|
||||||
|
|
||||||
// Fragment search a.k.a. swoop (as in `helm-swoop` in the Helm package of Emacs).
|
|
||||||
// Split a `query` {string} into its fragments {string[]}.
|
|
||||||
function makeQueryFragments(query) {
|
|
||||||
let fragments = query.trim().split(/\s+/).map(str => str.trim().toLowerCase()).filter(onlyUnique);
|
|
||||||
// fragments = fragments.filter( function(str) { return str.length >= 3; } ); // Helm does this, but perhaps better if we don't.
|
|
||||||
return fragments;
|
|
||||||
}
|
|
||||||
// Check whether `text` {string} includes all of the `fragments` {string[]}.
|
|
||||||
function matchFragments(fragments, text) {
|
|
||||||
if (!text || !text.toLowerCase) return false;
|
|
||||||
return fragments.every(item => text.toLowerCase().includes(item));
|
|
||||||
}
|
|
||||||
const fragments = makeQueryFragments(searchQuery);
|
|
||||||
// At least one chat message must match *all* the fragments.
|
|
||||||
// Currently, this doesn't match if the fragment matches are distributed across several chat messages.
|
|
||||||
return chatContent && Object.values(chatContent).some(message => matchFragments(fragments, message?.mes));
|
|
||||||
});
|
|
||||||
|
|
||||||
console.debug(filteredData);
|
|
||||||
for (const value of filteredData.values()) {
|
|
||||||
let strlen = 300;
|
|
||||||
let mes = value['mes'];
|
|
||||||
|
|
||||||
if (mes !== undefined) {
|
|
||||||
if (mes.length > strlen) {
|
|
||||||
mes = '...' + mes.substring(mes.length - strlen);
|
|
||||||
}
|
|
||||||
const fileSize = value['file_size'];
|
|
||||||
const fileName = value['file_name'];
|
|
||||||
const chatItems = rawChats[fileName].length;
|
|
||||||
const timestamp = timestampToMoment(value['last_mes']).format('lll');
|
|
||||||
const template = $('#past_chat_template .select_chat_block_wrapper').clone();
|
|
||||||
template.find('.select_chat_block').attr('file_name', fileName);
|
|
||||||
template.find('.avatar img').attr('src', avatarImg);
|
|
||||||
template.find('.select_chat_block_filename').text(fileName);
|
|
||||||
template.find('.chat_file_size').text(`(${fileSize},`);
|
|
||||||
template.find('.chat_messages_num').text(`${chatItems}💬)`);
|
|
||||||
template.find('.select_chat_block_mes').text(mes);
|
|
||||||
template.find('.PastChat_cross').attr('file_name', fileName);
|
|
||||||
template.find('.chat_messages_date').text(timestamp);
|
|
||||||
|
|
||||||
if (selected_group) {
|
|
||||||
template.find('.avatar img').replaceWith(getGroupAvatar(group));
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#select_chat_div').append(template);
|
|
||||||
|
|
||||||
if (currentChat === fileName.toString().replace('.jsonl', '')) {
|
|
||||||
$('#select_chat_div').find('.select_chat_block:last').attr('highlight', String(true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
displayChats(''); // Display all by default
|
|
||||||
|
|
||||||
const debouncedDisplay = debounce((searchQuery) => {
|
const debouncedDisplay = debounce((searchQuery) => {
|
||||||
displayChats(searchQuery);
|
displayChats(searchQuery, currentChat, displayName, avatarImg, selected_group);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Define the search input listener
|
// Define the search input listener
|
||||||
@ -7136,6 +7052,53 @@ export async function displayPastChats() {
|
|||||||
}, 200);
|
}, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function displayChats(searchQuery, currentChat, displayName, avatarImg, selected_group) {
|
||||||
|
try {
|
||||||
|
const trimExtension = (fileName) => String(fileName).replace('.jsonl', '');
|
||||||
|
|
||||||
|
const response = await fetch('/api/chats/search', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getRequestHeaders(),
|
||||||
|
body: JSON.stringify({
|
||||||
|
query: searchQuery,
|
||||||
|
avatar_url: selected_group ? null : characters[this_chid].avatar,
|
||||||
|
group_id: selected_group || null,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Search failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredData = await response.json();
|
||||||
|
$('#select_chat_div').empty();
|
||||||
|
|
||||||
|
filteredData.sort((a, b) => sortMoments(timestampToMoment(a.last_mes), timestampToMoment(b.last_mes)));
|
||||||
|
|
||||||
|
for (const chat of filteredData) {
|
||||||
|
const isSelected = trimExtension(currentChat) === trimExtension(chat.file_name);
|
||||||
|
const template = $('#past_chat_template .select_chat_block_wrapper').clone();
|
||||||
|
template.find('.select_chat_block').attr('file_name', chat.file_name);
|
||||||
|
template.find('.avatar img').attr('src', avatarImg);
|
||||||
|
template.find('.select_chat_block_filename').text(chat.file_name);
|
||||||
|
template.find('.chat_file_size').text(`(${chat.file_size},`);
|
||||||
|
template.find('.chat_messages_num').text(`${chat.message_count} 💬)`);
|
||||||
|
template.find('.select_chat_block_mes').text(chat.preview_message);
|
||||||
|
template.find('.PastChat_cross').attr('file_name', chat.file_name);
|
||||||
|
template.find('.chat_messages_date').text(timestampToMoment(chat.last_mes).format('lll'));
|
||||||
|
|
||||||
|
if (isSelected) {
|
||||||
|
template.find('.select_chat_block').attr('highlight', String(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#select_chat_div').append(template);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading chats:', error);
|
||||||
|
toastr.error('Could not load chat data. Try reloading the page.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function selectRightMenuWithAnimation(selectedMenuId) {
|
export function selectRightMenuWithAnimation(selectedMenuId) {
|
||||||
const displayModes = {
|
const displayModes = {
|
||||||
'rm_group_chats_block': 'flex',
|
'rm_group_chats_block': 'flex',
|
||||||
@ -10418,8 +10381,6 @@ jQuery(async function () {
|
|||||||
easing: animation_easing,
|
easing: animation_easing,
|
||||||
});
|
});
|
||||||
setTimeout(function () { $('#shadow_select_chat_popup').css('display', 'none'); }, animation_duration);
|
setTimeout(function () { $('#shadow_select_chat_popup').css('display', 'none'); }, animation_duration);
|
||||||
//$("#shadow_select_chat_popup").css("display", "none");
|
|
||||||
$('#load_select_chat_div').css('display', 'block');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (navigator.clipboard === undefined) {
|
if (navigator.clipboard === undefined) {
|
||||||
@ -10819,7 +10780,6 @@ jQuery(async function () {
|
|||||||
var formData = new FormData($('#form_import_chat').get(0));
|
var formData = new FormData($('#form_import_chat').get(0));
|
||||||
formData.append('user_name', name1);
|
formData.append('user_name', name1);
|
||||||
$('#select_chat_div').html('');
|
$('#select_chat_div').html('');
|
||||||
$('#load_select_chat_div').css('display', 'block');
|
|
||||||
|
|
||||||
if (selected_group) {
|
if (selected_group) {
|
||||||
await importGroupChat(formData);
|
await importGroupChat(formData);
|
||||||
|
@ -636,7 +636,6 @@ export function initBookmarks() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$('#shadow_select_chat_popup').css('display', 'none');
|
$('#shadow_select_chat_popup').css('display', 'none');
|
||||||
$('#load_select_chat_div').css('display', 'block');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on('click', '.mes_create_bookmark', async function () {
|
$(document).on('click', '.mes_create_bookmark', async function () {
|
||||||
|
@ -4244,8 +4244,8 @@ h5 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#select_chat_popup {
|
#select_chat_popup {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-rows: auto auto;
|
flex-direction: column;
|
||||||
max-width: var(--sheldWidth);
|
max-width: var(--sheldWidth);
|
||||||
height: min-content;
|
height: min-content;
|
||||||
max-height: calc(100vh - var(--topBarBlockSize));
|
max-height: calc(100vh - var(--topBarBlockSize));
|
||||||
@ -4265,7 +4265,7 @@ h5 {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
background-color: var(--SmartThemeBlurTintColor);
|
background-color: var(--SmartThemeBlurTintColor);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
overflow-y: auto;
|
overflow-y: hidden;
|
||||||
border: 1px solid var(--SmartThemeBorderColor);
|
border: 1px solid var(--SmartThemeBorderColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4273,20 +4273,10 @@ h5 {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
#load_select_chat_div {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 154px;
|
|
||||||
left: 174px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#load_select_chat_div img {
|
|
||||||
width: 80px;
|
|
||||||
height: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#select_chat_div {
|
#select_chat_div {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: min-content;
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#select_chat_div hr {
|
#select_chat_div hr {
|
||||||
@ -4333,6 +4323,11 @@ h5 {
|
|||||||
|
|
||||||
.select_chat_block_mes {
|
.select_chat_block_mes {
|
||||||
font-size: calc(var(--mainFontSize) - .25rem);
|
font-size: calc(var(--mainFontSize) - .25rem);
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
line-clamp: 3;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.PastChat_cross {
|
.PastChat_cross {
|
||||||
|
@ -52,6 +52,37 @@ function getBackupFunction(handle) {
|
|||||||
return backupFunctions.get(handle);
|
return backupFunctions.get(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a byte size into a human-readable string with units
|
||||||
|
* @param {number} bytes - The size in bytes to format
|
||||||
|
* @returns {string} The formatted string (e.g., "1.5 MB")
|
||||||
|
*/
|
||||||
|
function formatBytes(bytes) {
|
||||||
|
if (bytes === 0) return '0 B';
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a preview message from an array of chat messages
|
||||||
|
* @param {Array<Object>} messages - Array of chat messages, each with a 'mes' property
|
||||||
|
* @returns {string} A truncated preview of the last message or empty string if no messages
|
||||||
|
*/
|
||||||
|
function getPreviewMessage(messages) {
|
||||||
|
const strlen = 400;
|
||||||
|
const lastMessage = messages[messages.length - 1]?.mes;
|
||||||
|
|
||||||
|
if (!lastMessage) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastMessage.length > strlen
|
||||||
|
? '...' + lastMessage.substring(lastMessage.length - strlen)
|
||||||
|
: lastMessage;
|
||||||
|
}
|
||||||
|
|
||||||
process.on('exit', () => {
|
process.on('exit', () => {
|
||||||
for (const func of backupFunctions.values()) {
|
for (const func of backupFunctions.values()) {
|
||||||
func.flush();
|
func.flush();
|
||||||
@ -516,3 +547,119 @@ router.post('/group/save', jsonParser, (request, response) => {
|
|||||||
getBackupFunction(request.user.profile.handle)(request.user.directories.backups, String(id), jsonlData);
|
getBackupFunction(request.user.profile.handle)(request.user.directories.backups, String(id), jsonlData);
|
||||||
return response.send({ ok: true });
|
return response.send({ ok: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post('/search', jsonParser, function (request, response) {
|
||||||
|
try {
|
||||||
|
const { query, avatar_url, group_id } = request.body;
|
||||||
|
let chatFiles = [];
|
||||||
|
|
||||||
|
if (group_id) {
|
||||||
|
// Find group's chat IDs first
|
||||||
|
const groupDir = path.join(request.user.directories.groups);
|
||||||
|
const groupFiles = fs.readdirSync(groupDir)
|
||||||
|
.filter(file => file.endsWith('.json'));
|
||||||
|
|
||||||
|
let targetGroup;
|
||||||
|
for (const groupFile of groupFiles) {
|
||||||
|
const groupData = JSON.parse(fs.readFileSync(path.join(groupDir, groupFile), 'utf8'));
|
||||||
|
if (groupData.id === group_id) {
|
||||||
|
targetGroup = groupData;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetGroup?.chats) {
|
||||||
|
return response.send([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find group chat files for given group ID
|
||||||
|
const groupChatsDir = path.join(request.user.directories.groupChats);
|
||||||
|
chatFiles = targetGroup.chats
|
||||||
|
.map(chatId => {
|
||||||
|
const filePath = path.join(groupChatsDir, `${chatId}.jsonl`);
|
||||||
|
if (!fs.existsSync(filePath)) return null;
|
||||||
|
const stats = fs.statSync(filePath);
|
||||||
|
return {
|
||||||
|
file_name: chatId,
|
||||||
|
file_size: formatBytes(stats.size),
|
||||||
|
path: filePath,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(x => x);
|
||||||
|
} else {
|
||||||
|
// Regular character chat directory
|
||||||
|
const character_name = avatar_url.replace('.png', '');
|
||||||
|
const directoryPath = path.join(request.user.directories.chats, character_name);
|
||||||
|
|
||||||
|
if (!fs.existsSync(directoryPath)) {
|
||||||
|
return response.send([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
chatFiles = fs.readdirSync(directoryPath)
|
||||||
|
.filter(file => file.endsWith('.jsonl'))
|
||||||
|
.map(fileName => {
|
||||||
|
const filePath = path.join(directoryPath, fileName);
|
||||||
|
const stats = fs.statSync(filePath);
|
||||||
|
return {
|
||||||
|
file_name: fileName,
|
||||||
|
file_size: formatBytes(stats.size),
|
||||||
|
path: filePath,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
// Search logic
|
||||||
|
for (const chatFile of chatFiles) {
|
||||||
|
const data = fs.readFileSync(chatFile.path, 'utf8');
|
||||||
|
const messages = data.split('\n')
|
||||||
|
.map(line => { try { return JSON.parse(line); } catch (_) { return null; } })
|
||||||
|
.filter(x => x && typeof x.mes === 'string');
|
||||||
|
|
||||||
|
if (messages.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastMessage = messages[messages.length - 1];
|
||||||
|
const lastMesDate = lastMessage?.send_date || new Date().toISOString();
|
||||||
|
|
||||||
|
// If no search query, just return metadata
|
||||||
|
if (!query) {
|
||||||
|
results.push({
|
||||||
|
file_name: chatFile.file_name,
|
||||||
|
file_size: chatFile.file_size,
|
||||||
|
message_count: messages.length,
|
||||||
|
last_mes: lastMesDate,
|
||||||
|
preview_message: getPreviewMessage(messages),
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search through messages
|
||||||
|
const fragments = query.trim().toLowerCase().split(/\s+/).filter(x => x);
|
||||||
|
const hasMatch = messages.some(message => {
|
||||||
|
const text = message?.mes?.toLowerCase();
|
||||||
|
return text && fragments.every(fragment => text.includes(fragment));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hasMatch) {
|
||||||
|
results.push({
|
||||||
|
file_name: chatFile.file_name,
|
||||||
|
file_size: chatFile.file_size,
|
||||||
|
message_count: messages.length,
|
||||||
|
last_mes: lastMesDate,
|
||||||
|
preview_message: getPreviewMessage(messages),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by last message date descending
|
||||||
|
results.sort((a, b) => new Date(b.last_mes) - new Date(a.last_mes));
|
||||||
|
return response.send(results);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Chat search error:', error);
|
||||||
|
return response.status(500).json({ error: 'Search failed' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user