From bb2f553c46b6bd3048e3bc269cf2ba911fa3eca4 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Wed, 15 May 2024 02:06:11 +0200 Subject: [PATCH 1/9] Tag Folders folder filter showing only folders --- public/script.js | 16 +++++++++++----- public/scripts/filters.js | 38 +++++++++++++++++++++++++++++++------- public/scripts/tags.js | 5 ++--- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/public/script.js b/public/script.js index 8dd9b427e..82f7f801e 100644 --- a/public/script.js +++ b/public/script.js @@ -191,7 +191,7 @@ import { NOTE_MODULE_NAME, initAuthorsNote, metadata_keys, setFloatingPrompt, sh import { registerPromptManagerMigration } from './scripts/PromptManager.js'; import { getRegexedString, regex_placement } from './scripts/extensions/regex/engine.js'; import { initLogprobs, saveLogprobsForActiveMessage } from './scripts/logprobs.js'; -import { FILTER_TYPES, FilterHelper } from './scripts/filters.js'; +import { FILTER_STATES, FILTER_TYPES, FilterHelper, isFilterState } from './scripts/filters.js'; import { getCfgPrompt, getGuidanceScale, initCfg } from './scripts/cfg-scale.js'; import { force_output_sequence, @@ -1387,7 +1387,7 @@ function verifyCharactersSearchSortRule() { * @typedef {object} Entity - Object representing a display entity * @property {Character|Group|import('./scripts/tags.js').Tag|*} item - The item * @property {string|number} id - The id - * @property {string} type - The type of this entity (character, group, tag) + * @property {'character'|'group'|'tag'} type - The type of this entity (character, group, tag) * @property {Entity[]} [entities] - An optional list of entities relevant for this item * @property {number} [hidden] - An optional number representing how many hidden entities this entity contains */ @@ -1462,7 +1462,8 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) { const subCount = subEntities.length; subEntities = filterByTagState(entities, { subForEntity: entity }); if (doFilter) { - subEntities = entitiesFilter.applyFilters(subEntities, { clearScoreCache: false }); + // sub entities filter "hacked" because folder filter should not be applied there, so even in "only folders" mode characters show up + subEntities = entitiesFilter.applyFilters(subEntities, { clearScoreCache: false, tempOverrides: { [FILTER_TYPES.FOLDER]: FILTER_STATES.UNDEFINED } }); } entity.entities = subEntities; entity.hidden = subCount - subEntities.length; @@ -1471,8 +1472,13 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) { // Second run filters, hiding whatever should be filtered later if (doFilter) { - entities = filterByTagState(entities, { globalDisplayFilters: true }); - entities = entitiesFilter.applyFilters(entities); + const beforeFinalEntities = filterByTagState(entities, { globalDisplayFilters: true }); + entities = entitiesFilter.applyFilters(beforeFinalEntities); + + // Magic for folder filter. If that one is enabled, and no folders are display anymore, we remove that filter to actually show the characters. + if (isFilterState(entitiesFilter.getFilterData(FILTER_TYPES.FOLDER), FILTER_STATES.SELECTED) && entities.filter(x => x.type == 'tag').length == 0) { + entities = entitiesFilter.applyFilters(beforeFinalEntities, { tempOverrides: { [FILTER_TYPES.FOLDER]: FILTER_STATES.UNDEFINED } }); + } } if (doSort) { diff --git a/public/scripts/filters.js b/public/scripts/filters.js index 09bd86cc3..adc27b349 100644 --- a/public/scripts/filters.js +++ b/public/scripts/filters.js @@ -258,9 +258,8 @@ export class FilterHelper { */ folderFilter(data) { const state = this.filterData[FILTER_TYPES.FOLDER]; - // Slightly different than the other filters, as a positive folder filter means it doesn't filter anything (folders get "not hidden" at another place), - // while a negative state should then filter out all folders. - const isFolder = entity => isFilterState(state, FILTER_STATES.SELECTED) ? true : entity.type === 'tag'; + // Filter directly on folder. Special rules on still displaying characters with active folder filter are implemented in 'getEntitiesList' directly. + const isFolder = entity => entity.type === 'tag'; return this.filterDataByState(data, state, isFolder); } @@ -342,15 +341,40 @@ export class FilterHelper { * Applies all filters to the given data. * @param {any[]} data - The data to filter. * @param {object} options - Optional call parameters - * @param {boolean|FilterType} [options.clearScoreCache=true] - Whether the score + * @param {boolean} [options.clearScoreCache=true] - Whether the score cache should be cleared. + * @param {Object.} [options.tempOverrides={}] - Temporarily override specific filters for this filter application * @returns {any[]} The filtered data. */ - applyFilters(data, { clearScoreCache = true } = {}) { + applyFilters(data, { clearScoreCache = true, tempOverrides = {} } = {}) { if (clearScoreCache) this.clearScoreCache(); - return Object.values(this.filterFunctions) - .reduce((data, fn) => fn(data), data); + + // Save original filter states + const originalStates = {}; + for (const key in tempOverrides) { + originalStates[key] = this.filterData[key]; + this.filterData[key] = tempOverrides[key]; + } + + try { + const result = Object.values(this.filterFunctions) + .reduce((data, fn) => fn(data), data); + + // Restore original filter states + for (const key in originalStates) { + this.filterData[key] = originalStates[key]; + } + + return result; + } catch (error) { + // Restore original filter states in case of an error + for (const key in originalStates) { + this.filterData[key] = originalStates[key]; + } + throw error; + } } + /** * Cache scores for a specific filter type * @param {FilterType} type - The type of data being cached diff --git a/public/scripts/tags.js b/public/scripts/tags.js index 64e83428c..4d637eba8 100644 --- a/public/scripts/tags.js +++ b/public/scripts/tags.js @@ -63,7 +63,7 @@ export const tag_filter_types = { const ACTIONABLE_TAGS = { FAV: { id: '1', sort_order: 1, name: 'Show only favorites', color: 'rgba(255, 255, 0, 0.5)', action: filterByFav, icon: 'fa-solid fa-star', class: 'filterByFavorites' }, GROUP: { id: '0', sort_order: 2, name: 'Show only groups', color: 'rgba(100, 100, 100, 0.5)', action: filterByGroups, icon: 'fa-solid fa-users', class: 'filterByGroups' }, - FOLDER: { id: '4', sort_order: 3, name: 'Always show folders', color: 'rgba(120, 120, 120, 0.5)', action: filterByFolder, icon: 'fa-solid fa-folder-plus', class: 'filterByFolder' }, + FOLDER: { id: '4', sort_order: 3, name: 'Show only folders', color: 'rgba(120, 120, 120, 0.5)', action: filterByFolder, icon: 'fa-solid fa-folder-plus', class: 'filterByFolder' }, VIEW: { id: '2', sort_order: 4, name: 'Manage tags', color: 'rgba(150, 100, 100, 0.5)', action: onViewTagsListClick, icon: 'fa-solid fa-gear', class: 'manageTags' }, HINT: { id: '3', sort_order: 5, name: 'Show Tag List', color: 'rgba(150, 100, 100, 0.5)', action: onTagListHintClick, icon: 'fa-solid fa-tags', class: 'showTagList' }, UNFILTER: { id: '5', sort_order: 6, name: 'Clear all filters', action: onClearAllFiltersClick, icon: 'fa-solid fa-filter-circle-xmark', class: 'clearAllFilters' }, @@ -174,9 +174,8 @@ function filterByTagState(entities, { globalDisplayFilters = false, subForEntity } // Hide folders that have 0 visible sub entities after the first filtering round - const alwaysFolder = isFilterState(entitiesFilter.getFilterData(FILTER_TYPES.FOLDER), FILTER_STATES.SELECTED); if (entity.type === 'tag') { - return alwaysFolder || entity.entities.length > 0; + return entity.entities.length > 0; } return true; From 068b542c50456f91b4ec66d3081e0ed69c358f7b Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Wed, 15 May 2024 23:37:18 +0200 Subject: [PATCH 2/9] Tag folders "onboarding" icon (: --- public/css/tags.css | 3 ++- public/scripts/tags.js | 13 +++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/public/css/tags.css b/public/css/tags.css index 11806c69a..9075f659e 100644 --- a/public/css/tags.css +++ b/public/css/tags.css @@ -193,7 +193,8 @@ filter: brightness(75%) saturate(0.6); } -.tag_as_folder:hover { +.tag_as_folder:hover, +.tag_as_folder.flash { filter: brightness(150%) saturate(0.6) !important; } diff --git a/public/scripts/tags.js b/public/scripts/tags.js index 4d637eba8..389e06864 100644 --- a/public/scripts/tags.js +++ b/public/scripts/tags.js @@ -17,6 +17,7 @@ import { FILTER_TYPES, FILTER_STATES, DEFAULT_FILTER_STATE, isFilterState, Filte import { groupCandidatesFilter, groups, selected_group } from './group-chats.js'; import { download, onlyUnique, parseJsonFile, uuidv4, getSortableDelay, flashHighlight } from './utils.js'; import { power_user } from './power-user.js'; +import { debounce_timeout } from './constants.js'; export { TAG_FOLDER_TYPES, @@ -321,6 +322,13 @@ function filterByGroups(filterHelper) { * @param {FilterHelper} filterHelper Instance of FilterHelper class. */ function filterByFolder(filterHelper) { + if (!power_user.bogus_folders) { + $('#bogus_folders').prop('checked', true).trigger('input'); + onViewTagsListClick(); + flashHighlight($('#dialogue_popup .tag_as_folder, #dialogue_popup .tag_folder_indicator')); + return; + } + const state = toggleTagThreeState($(this)); ACTIONABLE_TAGS.FOLDER.filter_state = state; filterHelper.setFilterData(FILTER_TYPES.FOLDER, state); @@ -882,8 +890,9 @@ function printTagFilters(type = tag_filter_types.character) { const FILTER_SELECTOR = type === tag_filter_types.character ? CHARACTER_FILTER_SELECTOR : GROUP_FILTER_SELECTOR; $(FILTER_SELECTOR).empty(); - // Print all action tags. (Exclude folder if that setting isn't chosen) - const actionTags = Object.values(ACTIONABLE_TAGS).filter(tag => power_user.bogus_folders || tag.id != ACTIONABLE_TAGS.FOLDER.id); + // Print all action tags. (Rework 'Folder' button to some kind of onboarding if no folders are enabled yet) + const actionTags = Object.values(ACTIONABLE_TAGS); + actionTags.find(x => x == ACTIONABLE_TAGS.FOLDER).name = power_user.bogus_folders ? 'Show only folders' : 'Enable \'Tags as Folder\'\n\nAllows characters to be grouped in folders by their assigned tags.\nTags have to be explicitly chosen as folder to show up.\n\nClick here to start'; printTagList($(FILTER_SELECTOR), { empty: false, sort: false, tags: actionTags, tagActionSelector: tag => tag.action, tagOptions: { isGeneralList: true } }); const inListActionTags = Object.values(InListActionable); From 7fbed26c26b45e72a8f57bf9ea66c7f084dc5f77 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Thu, 16 May 2024 00:49:26 +0300 Subject: [PATCH 3/9] #2245 Fix custom group avatar display --- public/scripts/group-chats.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index 672ac32a0..1fb95b736 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -637,7 +637,7 @@ function isValidImageUrl(url) { if (Object.keys(url).length === 0) { return false; } - return isDataURL(url) || (url && url.startsWith('user')); + return isDataURL(url) || (url && (url.startsWith('user') || url.startsWith('/user'))); } function getGroupAvatar(group) { @@ -1418,6 +1418,10 @@ function select_group_chats(groupId, skipAnimation) { * @returns {Promise} - A promise that resolves when the processing and upload is complete. */ async function uploadGroupAvatar(event) { + if (!(event.target instanceof HTMLInputElement) || !event.target.files.length) { + return; + } + const file = event.target.files[0]; if (!file) { From 012f70336f8d328cc2d79e7fa3ec0d45089007f8 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Thu, 16 May 2024 01:02:22 +0300 Subject: [PATCH 4/9] Prevent header from jumping a few pixels when switching from list to character view --- public/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/style.css b/public/style.css index 000761754..780a1e527 100644 --- a/public/style.css +++ b/public/style.css @@ -2814,7 +2814,7 @@ grammarly-extension { #result_info_text { display: flex; flex-direction: column; - line-height: 1; + line-height: 0.9; text-align: right; } From a6333f32851829714651465348f7b75f08bf4dd6 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Thu, 16 May 2024 00:05:03 +0200 Subject: [PATCH 5/9] Sort tag folder inline avatars too --- public/script.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/public/script.js b/public/script.js index 82f7f801e..1c6269455 100644 --- a/public/script.js +++ b/public/script.js @@ -1465,6 +1465,9 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) { // sub entities filter "hacked" because folder filter should not be applied there, so even in "only folders" mode characters show up subEntities = entitiesFilter.applyFilters(subEntities, { clearScoreCache: false, tempOverrides: { [FILTER_TYPES.FOLDER]: FILTER_STATES.UNDEFINED } }); } + if (doSort) { + sortEntitiesList(subEntities); + } entity.entities = subEntities; entity.hidden = subCount - subEntities.length; } From 1b23a62c135f38a9cf144e925c8cd275590ccc92 Mon Sep 17 00:00:00 2001 From: Vincent Castellano Date: Wed, 15 May 2024 23:16:25 -0700 Subject: [PATCH 6/9] Added TTS Audio Playback Speed Config --- public/scripts/extensions/tts/index.js | 27 ++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index 6d187c76b..bb45414fa 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -261,6 +261,7 @@ async function playAudioData(audioJob) { audioElement.addEventListener('ended', completeCurrentAudioJob); audioElement.addEventListener('canplay', () => { console.debug('Starting TTS playback'); + audioElement.playbackRate = extension_settings.tts.playback_rate; audioElement.play(); }); } @@ -538,6 +539,7 @@ const defaultSettings = { currentProvider: 'ElevenLabs', auto_generation: true, narrate_user: false, + playback_rate: 1 }; function setTtsStatus(status, success) { @@ -1022,6 +1024,20 @@ $(document).ready(function () { Pass Asterisks to TTS Engine +
+
+
+ Audio Playback Speed +
+
+
+ +
+
+ +
+
+

@@ -1046,6 +1062,17 @@ $(document).ready(function () { $('#tts_pass_asterisks').on('click', onPassAsterisksClick); $('#tts_auto_generation').on('click', onAutoGenerationClick); $('#tts_narrate_user').on('click', onNarrateUserClick); + + $('#playback_rate').val(extension_settings.tts.playback_rate); + $('#playback_rate_counter').val(Number(extension_settings.tts.playback_rate).toFixed(2)); + $('#playback_rate').on('input', function () { + const value = $(this).val(); + const formattedValue = Number(value).toFixed(2); + extension_settings.tts.playback_rate = value; + $('#playback_rate_counter').val(formattedValue); + saveSettingsDebounced(); + }); + $('#tts_voices').on('click', onTtsVoicesClick); for (const provider in ttsProviders) { $('#tts_provider').append($('