| 7025 | $('#select_chat_div').empty(); | 7025 | $('#select_chat_div').empty(); |
| 7026 | $('#select_chat_search').val('').off('input'); | 7026 | $('#select_chat_search').val('').off('input'); |
| 7027 | | 7027 | |
| 7028 | const data = await (selected_group ? getGroupPastChats(selected_group) : getPastCharacterChats()); | | |
| 7029 | | | |
| 7030 | if (!data) { | | |
| 7031 | toastr.error(t`Could not load chat data. Try reloading the page.`); | | |
| 7032 | return; | | |
| 7033 | } | | |
| 7034 | | | |
| 7035 | const chatDetails = getCurrentChatDetails(); | 7028 | const chatDetails = getCurrentChatDetails(); |
| 7036 | const group = chatDetails.group; | | |
| 7037 | const currentChat = chatDetails.sessionName; | 7029 | const currentChat = chatDetails.sessionName; |
| 7038 | const displayName = chatDetails.characterName; | 7030 | const displayName = chatDetails.characterName; |
| 7039 | const avatarImg = chatDetails.avatarImgURL; | 7031 | const avatarImg = chatDetails.avatarImgURL; |
| 7040 | | 7032 | |
| 7041 | const rawChats = await getChatsFromFiles(data, selected_group); | 7033 | await displayChats('', currentChat, displayName, avatarImg, selected_group); |
| 7042 | | | |
| 7043 | // Sort by last message date descending | | |
| 7044 | data.sort((a, b) => sortMoments(timestampToMoment(a.last_mes), timestampToMoment(b.last_mes))); | | |
| 7045 | console.log(data); | | |
| 7046 | $('#load_select_chat_div').css('display', 'none'); | | |
| 7047 | $('#ChatHistoryCharName').text(`${displayName}'s `); | | |
| 7048 | | 7034 | |
| 7049 | const displayChats = (searchQuery) => { | 7035 | const debouncedDisplay = debounce((searchQuery) => { |
| 7050 | $('#select_chat_div').empty(); // Clear the current chats before appending filtered chats | 7036 | displayChats(searchQuery, currentChat, displayName, avatarImg, selected_group); |
| | 7037 | }); |
| 7051 | | 7038 | |
| 7052 | const filteredData = data.filter(chat => { | 7039 | // Define the search input listener |
| 7053 | const fileName = chat['file_name']; | 7040 | $('#select_chat_search').on('input', function () { |
| 7054 | const chatContent = rawChats[fileName]; | 7041 | const searchQuery = $(this).val(); |
| | 7042 | debouncedDisplay(searchQuery); |
| | 7043 | }); |
| 7055 | | 7044 | |
| 7056 | // Make sure empty chats are displayed when there is no search query | 7045 | // UX convenience: Focus the search field when the Manage Chat Files view opens. |
| 7057 | if (Array.isArray(chatContent) && !chatContent.length && !searchQuery) { | 7046 | setTimeout(function () { |
| 7058 | return true; | 7047 | const textSearchElement = $('#select_chat_search'); |
| | 7048 | textSearchElement.click(); |
| | 7049 | textSearchElement.focus(); |
| | 7050 | textSearchElement.select(); // select content (if any) for easy erasing |
| | 7051 | }, 200); |
| 7059 | } | 7052 | } |
| 7060 | | 7053 | |
| 7061 | // // Uncomment this to return to old behavior (classical full-substring search). | 7054 | async function displayChats(searchQuery, currentChat, displayName, avatarImg, selected_group) { |
| 7062 | // return chatContent && Object.values(chatContent).some(message => message?.mes?.toLowerCase()?.includes(searchQuery.toLowerCase())); | 7055 | try { |
| | 7056 | const response = await fetch('/api/chats/search', { |
| | 7057 | method: 'POST', |
| | 7058 | headers: getRequestHeaders(), |
| | 7059 | body: JSON.stringify({ |
| | 7060 | query: searchQuery, |
| | 7061 | avatar_url: selected_group ? null : characters[this_chid].avatar, |
| | 7062 | group_id: selected_group || null |
| | 7063 | }), |
| | 7064 | }); |
| 7063 | | 7065 | |
| 7064 | // Fragment search a.k.a. swoop (as in `helm-swoop` in the Helm package of Emacs). | 7066 | if (!response.ok) { |
| 7065 | // Split a `query` {string} into its fragments {string[]}. | 7067 | throw new Error('Search failed'); |
| 7066 | function makeQueryFragments(query) { | | |
| 7067 | let fragments = query.trim().split(/\s+/).map(str => str.trim().toLowerCase()).filter(onlyUnique); | | |
| 7068 | // fragments = fragments.filter( function(str) { return str.length >= 3; } ); // Helm does this, but perhaps better if we don't. | | |
| 7069 | return fragments; | | |
| 7070 | } | | |
| 7071 | // Check whether `text` {string} includes all of the `fragments` {string[]}. | | |
| 7072 | function matchFragments(fragments, text) { | | |
| 7073 | if (!text || !text.toLowerCase) return false; | | |
| 7074 | return fragments.every(item => text.toLowerCase().includes(item)); | | |
| 7075 | } | 7068 | } |
| 7076 | const fragments = makeQueryFragments(searchQuery); | | |
| 7077 | // At least one chat message must match *all* the fragments. | | |
| 7078 | // Currently, this doesn't match if the fragment matches are distributed across several chat messages. | | |
| 7079 | return chatContent && Object.values(chatContent).some(message => matchFragments(fragments, message?.mes)); | | |
| 7080 | }); | | |
| 7081 | | 7069 | |
| 7082 | console.debug(filteredData); | 7070 | const filteredData = await response.json(); |
| 7083 | for (const value of filteredData.values()) { | 7071 | $('#select_chat_div').empty(); |
| 7084 | let strlen = 300; | | |
| 7085 | let mes = value['mes']; | | |
| 7086 | | 7072 | |
| 7087 | if (mes !== undefined) { | 7073 | for (const chat of filteredData) { |
| 7088 | if (mes.length > strlen) { | | |
| 7089 | mes = '...' + mes.substring(mes.length - strlen); | | |
| 7090 | } | | |
| 7091 | const fileSize = value['file_size']; | | |
| 7092 | const fileName = value['file_name']; | | |
| 7093 | const chatItems = rawChats[fileName].length; | | |
| 7094 | const timestamp = timestampToMoment(value['last_mes']).format('lll'); | | |
| 7095 | const template = $('#past_chat_template .select_chat_block_wrapper').clone(); | 7074 | const template = $('#past_chat_template .select_chat_block_wrapper').clone(); |
| 7096 | template.find('.select_chat_block').attr('file_name', fileName); | 7075 | template.find('.select_chat_block').attr('file_name', chat.file_name); |
| 7097 | template.find('.avatar img').attr('src', avatarImg); | 7076 | template.find('.avatar img').attr('src', avatarImg); |
| 7098 | template.find('.select_chat_block_filename').text(fileName); | 7077 | template.find('.select_chat_block_filename').text(chat.file_name); |
| 7099 | template.find('.chat_file_size').text(`(${fileSize},`); | 7078 | template.find('.chat_file_size').text(`(${chat.file_size},`); |
| 7100 | template.find('.chat_messages_num').text(`${chatItems}💬)`); | 7079 | template.find('.chat_messages_num').text(`${chat.message_count}💬)`); |
| 7101 | template.find('.select_chat_block_mes').text(mes); | 7080 | template.find('.select_chat_block_mes').text(chat.preview_message); |
| 7102 | template.find('.PastChat_cross').attr('file_name', fileName); | 7081 | template.find('.PastChat_cross').attr('file_name', chat.file_name); |
| 7103 | template.find('.chat_messages_date').text(timestamp); | 7082 | template.find('.chat_messages_date').text(timestampToMoment(chat.last_mes).format('lll')); |
| 7104 | | | |
| 7105 | if (selected_group) { | | |
| 7106 | template.find('.avatar img').replaceWith(getGroupAvatar(group)); | | |
| 7107 | } | | |
| 7108 | | 7083 | |
| 7109 | $('#select_chat_div').append(template); | 7084 | $('#select_chat_div').append(template); |
| 7110 | | 7085 | |
| 7111 | if (currentChat === fileName.toString().replace('.jsonl', '')) { | 7086 | if (currentChat === chat.file_name) { |
| 7112 | $('#select_chat_div').find('.select_chat_block:last').attr('highlight', String(true)); | 7087 | $('#select_chat_div').find('.select_chat_block:last').attr('highlight', String(true)); |
| 7113 | } | 7088 | } |
| 7114 | } | 7089 | } |
| | 7090 | } catch (error) { |
| | 7091 | console.error('Error loading chats:', error); |
| | 7092 | toastr.error('Could not load chat data. Try reloading the page.'); |
| 7115 | } | 7093 | } |
| 7116 | }; | | |
| 7117 | displayChats(''); // Display all by default | | |
| 7118 | | | |
| 7119 | const debouncedDisplay = debounce((searchQuery) => { | | |
| 7120 | displayChats(searchQuery); | | |
| 7121 | }); | | |
| 7122 | | | |
| 7123 | // Define the search input listener | | |
| 7124 | $('#select_chat_search').on('input', function () { | | |
| 7125 | const searchQuery = $(this).val(); | | |
| 7126 | debouncedDisplay(searchQuery); | | |
| 7127 | }); | | |
| 7128 | | | |
| 7129 | // UX convenience: Focus the search field when the Manage Chat Files view opens. | | |
| 7130 | setTimeout(function () { | | |
| 7131 | const textSearchElement = $('#select_chat_search'); | | |
| 7132 | textSearchElement.click(); | | |
| 7133 | textSearchElement.focus(); | | |
| 7134 | textSearchElement.select(); // select content (if any) for easy erasing | | |
| 7135 | }, 200); | | |
| 7136 | } | 7094 | } |
| 7137 | | 7095 | |
| 7138 | export function selectRightMenuWithAnimation(selectedMenuId) { | 7096 | export function selectRightMenuWithAnimation(selectedMenuId) { |