From d1a654f41f3021b117321bcdbef046daa7f85084 Mon Sep 17 00:00:00 2001 From: AlpinDale Date: Sun, 1 Dec 2024 02:37:11 +0000 Subject: [PATCH 01/70] add to index.html --- public/index.html | 67 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/public/index.html b/public/index.html index c00ab84f7..e28179009 100644 --- a/public/index.html +++ b/public/index.html @@ -1718,6 +1718,73 @@ Load default order +
+
+
+ Sampler Order +
+
+ Aphrodite only. Samplers will be applied in a top-down order. Use with caution. +
+
+
+ Top P & Top K + 0 +
+
+ Top A + 1 +
+
+ Min P + 2 +
+
+ Tail Free Sampling + 3 +
+
+ Typical P + 4 +
+
+ Dynatemp & Temperature + 5 +
+
+ Penalties + 6 +
+
+ DRY + 7 +
+
+ No Repeat Ngram + 8 +
+
+ Top Nsigma + 9 +
+
+ Eta Cutoff + 10 +
+
+ Epsilon Cutoff + 11 +
+
+ Cubic & Quadratic Sampling + 12 +
+
+ XTC + 13 +
+
+

From af582f43a65a021f53b908f0a507d55dce8c1e34 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 2 Dec 2024 02:52:00 +0200 Subject: [PATCH 02/70] [wip] Persona lorebook --- public/index.html | 22 +------ public/scripts/personas.js | 61 ++++++++++++++++++- public/scripts/power-user.js | 3 +- public/scripts/templates/chatLorebook.html | 18 ++++++ public/scripts/templates/personaLorebook.html | 18 ++++++ public/scripts/world-info.js | 55 ++++++++++++++--- 6 files changed, 146 insertions(+), 31 deletions(-) create mode 100644 public/scripts/templates/chatLorebook.html create mode 100644 public/scripts/templates/personaLorebook.html diff --git a/public/index.html b/public/index.html index 8e5b8b5fd..561b15d55 100644 --- a/public/index.html +++ b/public/index.html @@ -4813,6 +4813,8 @@

+

Persona Description

@@ -5539,26 +5541,6 @@
-
-
-
-

- Chat Lorebook for -

-
-
- - A selected World Info will be bound to this chat. When generating an AI reply, - it will be combined with the entries from global and character lorebooks. - -
-
- -
-
-
diff --git a/public/scripts/personas.js b/public/scripts/personas.js index 01747a528..57f6026c7 100644 --- a/public/scripts/personas.js +++ b/public/scripts/personas.js @@ -21,8 +21,10 @@ import { PAGINATION_TEMPLATE, debounce, delay, download, ensureImageFormatSuppor import { debounce_timeout } from './constants.js'; import { FILTER_TYPES, FilterHelper } from './filters.js'; import { selected_group } from './group-chats.js'; -import { POPUP_RESULT, POPUP_TYPE, Popup } from './popup.js'; +import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; import { t } from './i18n.js'; +import { world_names } from './world-info.js'; +import { renderTemplateAsync } from './templates.js'; let savePersonasPage = 0; const GRID_STORAGE_KEY = 'Personas_GridView'; @@ -375,6 +377,7 @@ export function initPersona(avatarId, personaName, personaDescription) { position: persona_description_positions.IN_PROMPT, depth: DEFAULT_DEPTH, role: DEFAULT_ROLE, + lorebook: '', }; saveSettingsDebounced(); @@ -418,6 +421,7 @@ export async function convertCharacterToPersona(characterId = null) { position: persona_description_positions.IN_PROMPT, depth: DEFAULT_DEPTH, role: DEFAULT_ROLE, + lorebook: '', }; // If the user is currently using this persona, update the description @@ -461,6 +465,7 @@ export function setPersonaDescription() { .val(power_user.persona_description_role) .find(`option[value="${power_user.persona_description_role}"]`) .prop('selected', String(true)); + $('#persona_lore_button').toggleClass('world_set', !!power_user.persona_description_lorebook); countPersonaDescriptionTokens(); } @@ -490,6 +495,7 @@ async function updatePersonaNameIfExists(avatarId, newName) { position: persona_description_positions.IN_PROMPT, depth: DEFAULT_DEPTH, role: DEFAULT_ROLE, + lorebook: '', }; console.log(`Created persona name for ${avatarId} as ${newName}`); } @@ -535,6 +541,7 @@ async function bindUserNameToPersona(e) { position: isCurrentPersona ? power_user.persona_description_position : persona_description_positions.IN_PROMPT, depth: isCurrentPersona ? power_user.persona_description_depth : DEFAULT_DEPTH, role: isCurrentPersona ? power_user.persona_description_role : DEFAULT_ROLE, + lorebook: isCurrentPersona ? power_user.persona_description_lorebook : '', }; } @@ -579,12 +586,20 @@ function selectCurrentPersona() { power_user.persona_description_position = descriptor.position ?? persona_description_positions.IN_PROMPT; power_user.persona_description_depth = descriptor.depth ?? DEFAULT_DEPTH; power_user.persona_description_role = descriptor.role ?? DEFAULT_ROLE; + power_user.persona_description_lorebook = descriptor.lorebook ?? ''; } else { power_user.persona_description = ''; power_user.persona_description_position = persona_description_positions.IN_PROMPT; power_user.persona_description_depth = DEFAULT_DEPTH; power_user.persona_description_role = DEFAULT_ROLE; - power_user.persona_descriptions[user_avatar] = { description: '', position: persona_description_positions.IN_PROMPT, depth: DEFAULT_DEPTH, role: DEFAULT_ROLE }; + power_user.persona_description_lorebook = ''; + power_user.persona_descriptions[user_avatar] = { + description: '', + position: persona_description_positions.IN_PROMPT, + depth: DEFAULT_DEPTH, + role: DEFAULT_ROLE, + lorebook: '', + }; } setPersonaDescription(); @@ -652,6 +667,7 @@ async function lockPersona() { position: persona_description_positions.IN_PROMPT, depth: DEFAULT_DEPTH, role: DEFAULT_ROLE, + lorebook: '', }; } @@ -731,6 +747,7 @@ function onPersonaDescriptionInput() { position: Number($('#persona_description_position').find(':selected').val()), depth: Number($('#persona_depth_value').val()), role: Number($('#persona_depth_role').find(':selected').val()), + lorebook: '', }; power_user.persona_descriptions[user_avatar] = object; } @@ -766,6 +783,43 @@ function onPersonaDescriptionDepthRoleInput() { saveSettingsDebounced(); } +async function onPersonaLoreButtonClick() { + const personaName = power_user.personas[user_avatar]; + const selectedLorebook = power_user.persona_description_lorebook; + + if (!personaName) { + toastr.warning(t`You must bind a name to this persona before you can set a lorebook.`, t`Persona name not set`); + return; + } + + const template = $(await renderTemplateAsync('personaLorebook')); + + const worldSelect = template.find('select'); + template.find('.persona_name').text(personaName); + + for (const worldName of world_names) { + const option = document.createElement('option'); + option.value = worldName; + option.innerText = worldName; + option.selected = selectedLorebook === worldName; + worldSelect.append(option); + } + + worldSelect.on('change', function () { + power_user.persona_description_lorebook = String($(this).val()); + + if (power_user.personas[user_avatar]) { + const object = getOrCreatePersonaDescriptor(); + object.lorebook = power_user.persona_description_lorebook; + } + + $('#persona_lore_button').toggleClass('world_set', !!power_user.persona_description_lorebook); + saveSettingsDebounced(); + }); + + await callGenericPopup(template, POPUP_TYPE.TEXT); +} + function onPersonaDescriptionPositionInput() { power_user.persona_description_position = Number( $('#persona_description_position').find(':selected').val(), @@ -789,6 +843,7 @@ function getOrCreatePersonaDescriptor() { position: power_user.persona_description_position, depth: power_user.persona_description_depth, role: power_user.persona_description_role, + lorebook: power_user.persona_description_lorebook, }; power_user.persona_descriptions[user_avatar] = object; } @@ -1038,6 +1093,7 @@ async function duplicatePersona(avatarId) { position: descriptor?.position ?? persona_description_positions.IN_PROMPT, depth: descriptor?.depth ?? DEFAULT_DEPTH, role: descriptor?.role ?? DEFAULT_ROLE, + lorebook: descriptor?.lorebook ?? '', }; await uploadUserAvatar(getUserAvatar(avatarId), newAvatarId); @@ -1055,6 +1111,7 @@ export function initPersonas() { $('#persona_description_position').on('input', onPersonaDescriptionPositionInput); $('#persona_depth_value').on('input', onPersonaDescriptionDepthValueInput); $('#persona_depth_role').on('input', onPersonaDescriptionDepthRoleInput); + $('#persona_lore_button').on('click', onPersonaLoreButtonClick); $('#personas_backup').on('click', onBackupPersonas); $('#personas_restore').on('click', () => $('#personas_restore_input').trigger('click')); $('#personas_restore_input').on('change', onPersonasRestoreInput); diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js index 504d53b13..31e486110 100644 --- a/public/scripts/power-user.js +++ b/public/scripts/power-user.js @@ -262,6 +262,7 @@ let power_user = { persona_description_position: persona_description_positions.IN_PROMPT, persona_description_role: 0, persona_description_depth: 2, + persona_description_lorebook: '', persona_show_notifications: true, persona_sort_order: 'asc', @@ -1925,7 +1926,7 @@ export function fuzzySearchPersonas(data, searchValue, fuzzySearchCaches = null) const mappedData = data.map(x => ({ key: x, name: power_user.personas[x] ?? '', - description: power_user.persona_descriptions[x]?.description ?? '' + description: power_user.persona_descriptions[x]?.description ?? '', })); const keys = [ diff --git a/public/scripts/templates/chatLorebook.html b/public/scripts/templates/chatLorebook.html new file mode 100644 index 000000000..c23c466e4 --- /dev/null +++ b/public/scripts/templates/chatLorebook.html @@ -0,0 +1,18 @@ +
+
+

+ Chat Lorebook for +

+
+
+ + A selected World Info will be bound to this chat. When generating an AI reply, + it will be combined with the entries from global and character lorebooks. + +
+
+ +
+
diff --git a/public/scripts/templates/personaLorebook.html b/public/scripts/templates/personaLorebook.html new file mode 100644 index 000000000..5a8a2b928 --- /dev/null +++ b/public/scripts/templates/personaLorebook.html @@ -0,0 +1,18 @@ +
+
+

+ Persona Lorebook for +

+
+
+ + A selected World Info will be bound to this persona. When generating an AI reply, + it will be combined with the entries from global, character and chat lorebooks. + +
+
+ +
+
diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js index 0f4351933..77ab6e249 100644 --- a/public/scripts/world-info.js +++ b/public/scripts/world-info.js @@ -3548,6 +3548,11 @@ async function getCharacterLore() { continue; } + if (power_user.persona_description_lorebook === worldName) { + console.debug(`[WI] Character ${name}'s world ${worldName} is already activated in persona lore! Skipping...`); + continue; + } + const data = await loadWorldInfo(worldName); const newEntries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(({ uid, ...rest }) => ({ uid, world: worldName, ...rest })) : []; entries = entries.concat(newEntries); @@ -3598,11 +3603,45 @@ async function getChatLore() { return entries; } +async function getPersonaLore() { + const chatWorld = chat_metadata[METADATA_KEY]; + const personaWorld = power_user.persona_description_lorebook; + + if (!personaWorld) { + return []; + } + + if (chatWorld === personaWorld) { + console.debug(`[WI] Persona world ${personaWorld} is already activated in chat world! Skipping...`); + return []; + } + + if (selected_world_info.includes(personaWorld)) { + console.debug(`[WI] Persona world ${personaWorld} is already activated in global world info! Skipping...`); + return []; + } + + const data = await loadWorldInfo(personaWorld); + const entries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(({ uid, ...rest }) => ({ uid, world: personaWorld, ...rest })) : []; + + console.debug(`[WI] Persona lore has ${entries.length} entries`, [personaWorld]); + + return entries; +} + export async function getSortedEntries() { try { - const globalLore = await getGlobalLore(); - const characterLore = await getCharacterLore(); - const chatLore = await getChatLore(); + const [ + globalLore, + characterLore, + chatLore, + personaLore, + ] = await Promise.all([ + getGlobalLore(), + getCharacterLore(), + getChatLore(), + getPersonaLore(), + ]); let entries; @@ -3622,8 +3661,8 @@ export async function getSortedEntries() { break; } - // Chat lore always goes first - entries = [...chatLore.sort(sortFn), ...entries]; + // Chat lore always goes first, then persona lore, then the rest + entries = [...chatLore.sort(sortFn), ...personaLore.sort(sortFn), ...entries]; // Calculate hash and parse decorators. Split maps to preserve old hashes. entries = entries.map((entry) => { @@ -4816,9 +4855,9 @@ export async function importWorldInfo(file) { }); } -export function assignLorebookToChat() { +export async function assignLorebookToChat() { const selectedName = chat_metadata[METADATA_KEY]; - const template = $('#chat_world_template .chat_world').clone(); + const template = $(await renderTemplateAsync('chatLorebook')); const worldSelect = template.find('select'); const chatName = template.find('.chat_name'); @@ -4846,7 +4885,7 @@ export function assignLorebookToChat() { saveMetadata(); }); - callPopup(template, 'text'); + callGenericPopup(template, POPUP_TYPE.TEXT); } jQuery(() => { From 80c8e83f09e9d2a743cb5e5e10d3285f1a93a1ef Mon Sep 17 00:00:00 2001 From: AlpinDale Date: Tue, 3 Dec 2024 01:46:51 +0000 Subject: [PATCH 03/70] use strings instead of IDs --- public/index.html | 96 +++++++++--------------------- public/scripts/samplerSelect.js | 13 ++++ public/scripts/textgen-settings.js | 34 +++++++++++ 3 files changed, 76 insertions(+), 67 deletions(-) diff --git a/public/index.html b/public/index.html index 5d1e54aec..c85ffc237 100644 --- a/public/index.html +++ b/public/index.html @@ -1718,73 +1718,6 @@ Load default order
-
-
-
- Sampler Order -
-
- Aphrodite only. Samplers will be applied in a top-down order. Use with caution. -
-
-
- Top P & Top K - 0 -
-
- Top A - 1 -
-
- Min P - 2 -
-
- Tail Free Sampling - 3 -
-
- Typical P - 4 -
-
- Dynatemp & Temperature - 5 -
-
- Penalties - 6 -
-
- DRY - 7 -
-
- No Repeat Ngram - 8 -
-
- Top Nsigma - 9 -
-
- Eta Cutoff - 10 -
-
- Epsilon Cutoff - 11 -
-
- Cubic & Quadratic Sampling - 12 -
-
- XTC - 13 -
-
-

@@ -1841,6 +1774,35 @@ Load default order

+
+
+

+ Sampler Order +
+

+
+ Aphrodite only. Determines the order of samplers. +
+
+
DRY
+
Penalties
+
No Repeat Ngram
+
Dynatemp & Temperature
+
Top Nsigma
+
Top P & Top K
+
Top A
+
Min P
+
Tail-Free Sampling
+
Eta Cutoff
+
Epsilon Cutoff
+
Typical P
+
Cubic and Quadratic Sampling
+
XTC
+
+ +
diff --git a/public/scripts/samplerSelect.js b/public/scripts/samplerSelect.js index c949b8e91..35f5629ba 100644 --- a/public/scripts/samplerSelect.js +++ b/public/scripts/samplerSelect.js @@ -129,6 +129,10 @@ function setSamplerListListeners() { relatedDOMElement = $('#sampler_priority_block_ooba'); } + if (samplerName === 'samplers_priorities') { //this is for aphrodite's sampler priority + relatedDOMElement = $('#sampler_priority_block_aphrodite'); + } + if (samplerName === 'penalty_alpha') { //contrastive search only has one sampler, does it need its own block? relatedDOMElement = $('#contrastiveSearchBlock'); } @@ -237,6 +241,11 @@ async function listSamplers(main_api, arrayOnly = false) { displayname = 'Ooba Sampler Priority Block'; } + if (sampler === 'samplers_priorities') { //this is for aphrodite's sampler priority + targetDOMelement = $('#sampler_priority_block_aphrodite'); + displayname = 'Aphrodite Sampler Priority Block'; + } + if (sampler === 'penalty_alpha') { //contrastive search only has one sampler, does it need its own block? targetDOMelement = $('#contrastiveSearchBlock'); displayname = 'Contrast Search Block'; @@ -373,6 +382,10 @@ export async function validateDisabledSamplers(redraw = false) { relatedDOMElement = $('#sampler_priority_block_ooba'); } + if (sampler === 'samplers_priorities') { //this is for aphrodite's sampler priority + relatedDOMElement = $('#sampler_priority_block_aphrodite'); + } + if (sampler === 'dry_multiplier') { relatedDOMElement = $('#dryBlock'); targetDisplayType = 'block'; diff --git a/public/scripts/textgen-settings.js b/public/scripts/textgen-settings.js index 0dfae364f..077bf6911 100644 --- a/public/scripts/textgen-settings.js +++ b/public/scripts/textgen-settings.js @@ -89,6 +89,22 @@ const OOBA_DEFAULT_ORDER = [ 'encoder_repetition_penalty', 'no_repeat_ngram', ]; +const APHRODITE_DEFAULT_ORDER = [ + 'dry', + 'penalties', + 'no_repeat_ngram', + 'temperature', + 'top_nsigma', + 'top_p_top_k', + 'top_a', + 'min_p', + 'tfs', + 'eta_cutoff', + 'epsilon_cutoff', + 'typical_p', + 'quadratic', + 'xtc' +]; const BIAS_KEY = '#textgenerationwebui_api-settings'; // Maybe let it be configurable in the future? @@ -170,6 +186,7 @@ const settings = { banned_tokens: '', sampler_priority: OOBA_DEFAULT_ORDER, samplers: LLAMACPP_DEFAULT_ORDER, + samplers_priorties: APHRODITE_DEFAULT_ORDER, ignore_eos_token: false, spaces_between_special_tokens: true, speculative_ngram: false, @@ -259,6 +276,7 @@ export const setting_names = [ 'sampler_order', 'sampler_priority', 'samplers', + 'samplers_priorities', 'n', 'logit_bias', 'custom_model', @@ -627,6 +645,13 @@ jQuery(function () { saveSettingsDebounced(); }); + $('#aphrodite_default_order').on('click', function () { + sortOobaItemsByOrder(APHRODITE_DEFAULT_ORDER); + settings.samplers_priorties = APHRODITE_DEFAULT_ORDER; + console.log('Default samplers order loaded:', settings.samplers_priorties); + saveSettingsDebounced(); + }); + $('#textgen_type').on('change', function () { const type = String($(this).val()); settings.type = type; @@ -835,6 +860,14 @@ function setSettingByName(setting, value, trigger) { return; } + if ('samplers_priority' === setting) { + value = Array.isArray(value) ? value : APHRODITE_DEFAULT_ORDER; + insertMissingArrayItems(APHRODITE_DEFAULT_ORDER, value); + sortOobaItemsByOrder(value); + settings.samplers_priorties = value; + return; + } + if ('samplers' === setting) { value = Array.isArray(value) ? value : LLAMACPP_DEFAULT_ORDER; insertMissingArrayItems(LLAMACPP_DEFAULT_ORDER, value); @@ -1259,6 +1292,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, 'nsigma': settings.nsigma, 'custom_token_bans': toIntArray(banned_tokens), 'no_repeat_ngram_size': settings.no_repeat_ngram_size, + 'sampler_priority': settings.samplers_priorties, }; if (settings.type === OPENROUTER) { From 9960db0ae2ef5cae6d75486294b61ef52d85a700 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Tue, 3 Dec 2024 22:48:10 +0200 Subject: [PATCH 04/70] Redesign extension manager --- public/css/extensions-panel.css | 38 +++++++- public/scripts/extensions.js | 153 ++++++++++++++++++++------------ 2 files changed, 131 insertions(+), 60 deletions(-) diff --git a/public/css/extensions-panel.css b/public/css/extensions-panel.css index da005bd70..6e0da7b3f 100644 --- a/public/css/extensions-panel.css +++ b/public/css/extensions-panel.css @@ -65,7 +65,7 @@ label[for="extensions_autoconnect"] { } .extensions_info .extension_enabled { - color: green; + font-weight: bold; } .extensions_info .extension_disabled { @@ -76,6 +76,42 @@ label[for="extensions_autoconnect"] { color: gray; } +.extensions_info .extension_modules { + font-size: 0.8em; + font-weight: normal; +} + +.extensions_info .extension_block { + display: flex; + flex-wrap: wrap; + padding: 10px; + margin-bottom: 5px; + border: 1px solid var(--SmartThemeBorderColor); + border-radius: 10px; + align-items: center; + justify-content: space-between; + gap: 10px; +} + +.extensions_info .extension_name { + font-size: 1.05em; +} + +.extensions_info .extension_version { + opacity: 0.8; + font-size: 0.8em; + font-weight: normal; + margin-left: 5px; +} + +.extensions_info .extension_block a { + color: var(--SmartThemeBodyColor); +} + +.extensions_info .extension_name.update_available { + color: limegreen; +} + input.extension_missing[type="checkbox"] { opacity: 0.5; } diff --git a/public/scripts/extensions.js b/public/scripts/extensions.js index e9a45f540..325f69451 100644 --- a/public/scripts/extensions.js +++ b/public/scripts/extensions.js @@ -515,64 +515,64 @@ function addExtensionScript(name, manifest) { * @param {boolean} isDisabled - Whether the extension is disabled or not. * @param {boolean} isExternal - Whether the extension is external or not. * @param {string} checkboxClass - The class for the checkbox HTML element. - * @return {Promise} - The HTML string that represents the extension. + * @return {string} - The HTML string that represents the extension. */ -async function generateExtensionHtml(name, manifest, isActive, isDisabled, isExternal, checkboxClass) { +function generateExtensionHtml(name, manifest, isActive, isDisabled, isExternal, checkboxClass) { const displayName = manifest.display_name; let displayVersion = manifest.version ? ` v${manifest.version}` : ''; - let isUpToDate = true; - let updateButton = ''; + const externalId = name.replace('third-party', ''); let originHtml = ''; if (isExternal) { - let data = await getExtensionVersion(name.replace('third-party', '')); - let branch = data.currentBranchName; - let commitHash = data.currentCommitHash; - let origin = data.remoteUrl; - isUpToDate = data.isUpToDate; - displayVersion = ` (${branch}-${commitHash.substring(0, 7)})`; - updateButton = isUpToDate ? - `` : - ``; - originHtml = ``; + originHtml = ''; } let toggleElement = isActive || isDisabled ? `` : ``; - let deleteButton = isExternal ? `` : ''; - - // if external, wrap the name in a link to the repo - - let extensionHtml = `
-

- ${updateButton} - ${deleteButton} - ${originHtml} - - ${DOMPurify.sanitize(displayName)}${displayVersion} - - ${isExternal ? '' : ''} - - ${toggleElement} -

`; + let deleteButton = isExternal ? `` : ''; + let updateButton = isExternal ? `` : ''; + let modulesInfo = ''; if (isActive && Array.isArray(manifest.optional)) { const optional = new Set(manifest.optional); modules.forEach(x => optional.delete(x)); if (optional.size > 0) { const optionalString = DOMPurify.sanitize([...optional].join(', ')); - extensionHtml += `

Optional modules: ${optionalString}

`; + modulesInfo = `
Optional modules: ${optionalString}
`; } } else if (!isDisabled) { // Neither active nor disabled const requirements = new Set(manifest.requires); modules.forEach(x => requirements.delete(x)); if (requirements.size > 0) { const requirementsString = DOMPurify.sanitize([...requirements].join(', ')); - extensionHtml += `

Missing modules: ${requirementsString}

`; + modulesInfo = `
Missing modules: ${requirementsString}
`; } } + // if external, wrap the name in a link to the repo + + let extensionHtml = ` +
+
+ ${toggleElement} +
+
+ ${originHtml} + + ${DOMPurify.sanitize(displayName)} + ${displayVersion} + ${modulesInfo} + + ${isExternal ? '' : ''} +
+ +
+ ${updateButton} + ${deleteButton} +
+
`; + return extensionHtml; } @@ -580,9 +580,9 @@ async function generateExtensionHtml(name, manifest, isActive, isDisabled, isExt * Gets extension data and generates the corresponding HTML for displaying the extension. * * @param {Array} extension - An array where the first element is the extension name and the second element is the extension manifest. - * @return {Promise} - An object with 'isExternal' indicating whether the extension is external, and 'extensionHtml' for the extension's HTML string. + * @return {object} - An object with 'isExternal' indicating whether the extension is external, and 'extensionHtml' for the extension's HTML string. */ -async function getExtensionData(extension) { +function getExtensionData(extension) { const name = extension[0]; const manifest = extension[1]; const isActive = activeExtensions.has(name); @@ -591,7 +591,7 @@ async function getExtensionData(extension) { const checkboxClass = isDisabled ? 'checkbox_disabled' : ''; - const extensionHtml = await generateExtensionHtml(name, manifest, isActive, isDisabled, isExternal, checkboxClass); + const extensionHtml = generateExtensionHtml(name, manifest, isActive, isDisabled, isExternal, checkboxClass); return { isExternal, extensionHtml }; } @@ -616,40 +616,28 @@ function getModuleInformation() { async function showExtensionsDetails() { let popupPromise; try { - const htmlDefault = $('

Built-in Extensions:

'); - const htmlExternal = $('

Installed Extensions:

').addClass('opacity50p'); - const htmlLoading = $(`

+ const htmlDefault = $('

Built-in Extensions:

'); + const htmlExternal = $('

Installed Extensions:

'); + const htmlLoading = $(`
Loading third-party extensions... Please wait... -

`); + `); - /** @type {Promise[]} */ - const promises = []; - const extensions = Object.entries(manifests).sort((a, b) => a[1].loading_order - b[1].loading_order); + htmlExternal.append(htmlLoading); - for (const extension of extensions) { - promises.push(getExtensionData(extension)); - } + const extensions = Object.entries(manifests).sort((a, b) => a[1].loading_order - b[1].loading_order).map(getExtensionData); - promises.forEach(promise => { - promise.then(value => { - const { isExternal, extensionHtml } = value; - const container = isExternal ? htmlExternal : htmlDefault; - container.append(extensionHtml); - }); - }); - - Promise.allSettled(promises).then(() => { - htmlLoading.remove(); - htmlExternal.removeClass('opacity50p'); + extensions.forEach(value => { + const { isExternal, extensionHtml } = value; + const container = isExternal ? htmlExternal : htmlDefault; + container.append(extensionHtml); }); const html = $('
') .addClass('extensions_info') - .append(getModuleInformation()) .append(htmlDefault) - .append(htmlLoading) - .append(htmlExternal); + .append(htmlExternal) + .append(getModuleInformation()); /** @type {import('./popup.js').CustomPopupButton} */ const updateAllButton = { @@ -692,6 +680,7 @@ async function showExtensionsDetails() { }, }); popupPromise = popup.show(); + checkForUpdatesManual().finally(() => htmlLoading.remove()); } catch (error) { toastr.error('Error loading extensions. See browser console for details.'); console.error(error); @@ -873,6 +862,52 @@ export function doDailyExtensionUpdatesCheck() { }, 1); } +async function checkForUpdatesManual() { + const promises = []; + for (const id of Object.keys(manifests).filter(x => x.startsWith('third-party'))) { + const externalId = id.replace('third-party', ''); + const promise = new Promise(async (resolve, reject) => { + try { + const data = await getExtensionVersion(externalId); + const extensionBlock = document.querySelector(`.extension_block[data-name="${externalId}"]`); + if (extensionBlock) { + if (data.isUpToDate === false) { + const buttonElement = extensionBlock.querySelector('.btn_update'); + if (buttonElement) { + buttonElement.classList.remove('displayNone'); + } + const nameElement = extensionBlock.querySelector('.extension_name'); + if (nameElement) { + nameElement.classList.add('update_available'); + } + } + let branch = data.currentBranchName; + let commitHash = data.currentCommitHash; + let origin = data.remoteUrl; + + const originLink = extensionBlock.querySelector('a'); + if (originLink) { + originLink.href = origin; + originLink.target = '_blank'; + originLink.rel = 'noopener noreferrer'; + } + + const versionElement = extensionBlock.querySelector('.extension_version'); + if (versionElement) { + versionElement.textContent += ` (${branch}-${commitHash.substring(0, 7)})`; + } + } + resolve(); + } catch (error) { + console.error('Error checking for extension updates', error); + reject(); + } + }); + promises.push(promise); + } + return Promise.allSettled(promises); +} + /** * Checks if there are updates available for 3rd-party extensions. * @param {boolean} force Skip nag check From cb21162558c5299f113c75eb124d50155c966331 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Tue, 3 Dec 2024 22:56:19 +0200 Subject: [PATCH 05/70] No dangling promise --- public/scripts/world-info.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js index 77ab6e249..d1265ba7c 100644 --- a/public/scripts/world-info.js +++ b/public/scripts/world-info.js @@ -4885,7 +4885,7 @@ export async function assignLorebookToChat() { saveMetadata(); }); - callGenericPopup(template, POPUP_TYPE.TEXT); + return callGenericPopup(template, POPUP_TYPE.TEXT); } jQuery(() => { From a702dab68b4a23ad7acfa42394732cac1bb51764 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Wed, 4 Dec 2024 13:53:10 +0000 Subject: [PATCH 06/70] Alt+Click to open lorebook --- public/index.html | 4 ++-- public/scripts/personas.js | 13 +++++++++++-- public/scripts/world-info.js | 32 ++++++++++++++++++++++++++------ 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/public/index.html b/public/index.html index 986c95638..648286958 100644 --- a/public/index.html +++ b/public/index.html @@ -4813,7 +4813,7 @@ -
@@ -4931,7 +4931,7 @@ - + diff --git a/public/scripts/personas.js b/public/scripts/personas.js index 57f6026c7..930e87a4e 100644 --- a/public/scripts/personas.js +++ b/public/scripts/personas.js @@ -23,7 +23,7 @@ import { FILTER_TYPES, FilterHelper } from './filters.js'; import { selected_group } from './group-chats.js'; import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; import { t } from './i18n.js'; -import { world_names } from './world-info.js'; +import { openWorldInfoEditor, world_names } from './world-info.js'; import { renderTemplateAsync } from './templates.js'; let savePersonasPage = 0; @@ -783,7 +783,11 @@ function onPersonaDescriptionDepthRoleInput() { saveSettingsDebounced(); } -async function onPersonaLoreButtonClick() { +/** + * Opens a popup to set the lorebook for the current persona. + * @param {PointerEvent} event Click event + */ +async function onPersonaLoreButtonClick(event) { const personaName = power_user.personas[user_avatar]; const selectedLorebook = power_user.persona_description_lorebook; @@ -792,6 +796,11 @@ async function onPersonaLoreButtonClick() { return; } + if (event.altKey && selectedLorebook) { + openWorldInfoEditor(selectedLorebook); + return; + } + const template = $(await renderTemplateAsync('personaLorebook')); const worldSelect = template.find('select'); diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js index d1265ba7c..51426a260 100644 --- a/public/scripts/world-info.js +++ b/public/scripts/world-info.js @@ -4855,8 +4855,32 @@ export async function importWorldInfo(file) { }); } -export async function assignLorebookToChat() { +/** + * Forces the world info editor to open on a specific world. + * @param {string} worldName The name of the world to open + */ +export function openWorldInfoEditor(worldName) { + console.log(`Opening lorebook for ${worldName}`); + if (!$('#WorldInfo').is(':visible')) { + $('#WIDrawerIcon').trigger('click'); + } + const index = world_names.indexOf(worldName); + $('#world_editor_select').val(index).trigger('change'); +} + +/** + * Assigns a lorebook to the current chat. + * @param {PointerEvent} event Pointer event + * @returns {Promise} + */ +export async function assignLorebookToChat(event) { const selectedName = chat_metadata[METADATA_KEY]; + + if (selectedName && event.altKey) { + openWorldInfoEditor(selectedName); + return; + } + const template = $(await renderTemplateAsync('chatLorebook')); const worldSelect = template.find('select'); @@ -5036,11 +5060,7 @@ jQuery(() => { const worldName = characters[chid]?.data?.extensions?.world; const hasEmbed = checkEmbeddedWorld(chid); if (worldName && world_names.includes(worldName) && !event.shiftKey) { - if (!$('#WorldInfo').is(':visible')) { - $('#WIDrawerIcon').trigger('click'); - } - const index = world_names.indexOf(worldName); - $('#world_editor_select').val(index).trigger('change'); + openWorldInfoEditor(worldName); } else if (hasEmbed && !event.shiftKey) { await importEmbeddedWorldInfo(); saveCharacterDebounced(); From 23a10fdc227954128ccf1da74ad723b92dcab752 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Thu, 5 Dec 2024 15:32:30 +0000 Subject: [PATCH 07/70] Optimize past swipe counters performance --- public/index.html | 2 +- public/script.js | 11 +++++------ public/scripts/power-user.js | 4 ++-- public/style.css | 10 ++++++++++ 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/public/index.html b/public/index.html index d8296c1fb..32723dd05 100644 --- a/public/index.html +++ b/public/index.html @@ -4183,7 +4183,7 @@