This commit is contained in:
RossAscends
2023-07-06 01:30:41 +09:00
16 changed files with 811 additions and 162 deletions

View File

@@ -2344,6 +2344,9 @@
<label for="import_card_tags"><input id="import_card_tags" type="checkbox" /> <label for="import_card_tags"><input id="import_card_tags" type="checkbox" />
<span data-i18n="Import Card Tags">Import Card Tags</span> <span data-i18n="Import Card Tags">Import Card Tags</span>
</label> </label>
<label for="spoiler_free_mode"><input id="spoiler_free_mode" type="checkbox" />
<span data-i18n="Spoiler Free Mode">Spolier Free Mode</span>
</label>
<div class="inline-drawer wide100p flexFlowColumn"> <div class="inline-drawer wide100p flexFlowColumn">
<div class="inline-drawer-toggle inline-drawer-header"> <div class="inline-drawer-toggle inline-drawer-header">
@@ -2425,9 +2428,9 @@
<input id="extensions_details" class="alignitemsflexstart menu_button" type="button" value="Manage extensions"> <input id="extensions_details" class="alignitemsflexstart menu_button" type="button" value="Manage extensions">
</div> </div>
</div> </div>
<div id="extensions_settings" class="flex1 widthNatural"> <div id="extensions_settings" class="flex1 wide50p">
</div> </div>
<div id="extensions_settings2" class="flex1 widthNatural"> <div id="extensions_settings2" class="flex1 wide50p">
</div> </div>
</div> </div>
@@ -2627,6 +2630,18 @@
</div> </div>
</div> </div>
<textarea id="firstmessage_textarea" placeholder="This will be the first message from the character that starts every chat." class="marginBot5" name="first_mes" placeholder=""></textarea> <textarea id="firstmessage_textarea" placeholder="This will be the first message from the character that starts every chat." class="marginBot5" name="first_mes" placeholder=""></textarea>
<div id="spoiler_free_desc">
<div id="creators_notes_div" class="marginBot5 title_restorable">
<span data-i18n="Creator's Notes">Creator's Notes</span>
<div id="spoiler_free_desc_button" class="menu_button fa-solid fa-eye"
title="Show / Hide Description and First Message"></div>
</a>
</div>
<hr>
<div id="creator_notes_spoiler" placeholder="Creator's Notes" class="marginBot5" name="creator_notes_spoiler"></div>
<!-- A button to show / hide description_div and description_textarea and first_message_div and firstmessage_textarea-->
</div>
<!-- these divs are invisible and used for server communication purposes --> <!-- these divs are invisible and used for server communication purposes -->
<div id="hidden-divs"> <div id="hidden-divs">
@@ -3311,6 +3326,7 @@
<div title="Generate Image" class="sd_message_gen fa-solid fa-paintbrush"></div> <div title="Generate Image" class="sd_message_gen fa-solid fa-paintbrush"></div>
<div title="Narrate" class="mes_narrate fa-solid fa-bullhorn"></div> <div title="Narrate" class="mes_narrate fa-solid fa-bullhorn"></div>
<div title="Prompt" class="mes_prompt fa-solid fa-square-poll-horizontal "></div> <div title="Prompt" class="mes_prompt fa-solid fa-square-poll-horizontal "></div>
<div title="Create bookmark" class="mes_create_bookmark fa-regular fa-code-branch"></div>
<div title="Copy" class="mes_copy fa-solid fa-copy "></div> <div title="Copy" class="mes_copy fa-solid fa-copy "></div>
</div> </div>
<div title="Open bookmark chat" class="mes_bookmark fa-solid fa-bookmark"></div> <div title="Open bookmark chat" class="mes_bookmark fa-solid fa-bookmark"></div>

View File

@@ -101,7 +101,10 @@ import {
nai_settings, nai_settings,
} from "./scripts/nai-settings.js"; } from "./scripts/nai-settings.js";
import { showBookmarksButtons } from "./scripts/bookmarks.js"; import {
createNewBookmark,
showBookmarksButtons
} from "./scripts/bookmarks.js";
import { import {
horde_settings, horde_settings,
@@ -162,6 +165,7 @@ import { context_settings, loadContextTemplatesFromSettings } from "./scripts/co
import { markdownExclusionExt } from "./scripts/showdown-exclusion.js"; import { markdownExclusionExt } from "./scripts/showdown-exclusion.js";
import { NOTE_MODULE_NAME, metadata_keys, setFloatingPrompt, shouldWIAddPrompt } from "./scripts/extensions/floating-prompt/index.js"; import { NOTE_MODULE_NAME, metadata_keys, setFloatingPrompt, shouldWIAddPrompt } from "./scripts/extensions/floating-prompt/index.js";
import { deviceInfo } from "./scripts/RossAscends-mods.js"; import { deviceInfo } from "./scripts/RossAscends-mods.js";
import { getRegexedString, regex_placement } from "./scripts/extensions/regex/engine.js";
//exporting functions and vars for mods //exporting functions and vars for mods
export { export {
@@ -238,6 +242,26 @@ export {
// API OBJECT FOR EXTERNAL WIRING // API OBJECT FOR EXTERNAL WIRING
window["SillyTavern"] = {}; window["SillyTavern"] = {};
// Event source init
export const event_types = {
EXTRAS_CONNECTED: 'extras_connected',
MESSAGE_SWIPED: 'message_swiped',
MESSAGE_SENT: 'message_sent',
MESSAGE_RECEIVED: 'message_received',
MESSAGE_EDITED: 'message_edited',
MESSAGE_DELETED: 'message_deleted',
IMPERSONATE_READY: 'impersonate_ready',
CHAT_CHANGED: 'chat_id_changed',
GENERATION_STOPPED: 'generation_stopped',
EXTENSIONS_FIRST_LOAD: 'extensions_first_load',
SETTINGS_LOADED: 'settings_loaded',
SETTINGS_UPDATED: 'settings_updated',
GROUP_UPDATED: 'group_updated',
MOVABLE_PANELS_RESET: 'movable_panels_reset',
}
export const eventSource = new EventEmitter();
const gpt3 = new GPT3BrowserTokenizer({ type: 'gpt3' }); const gpt3 = new GPT3BrowserTokenizer({ type: 'gpt3' });
hljs.addPlugin({ "before:highlightElement": ({ el }) => { el.textContent = el.innerText } }); hljs.addPlugin({ "before:highlightElement": ({ el }) => { el.textContent = el.innerText } });
@@ -479,23 +503,6 @@ const system_messages = {
}, },
}; };
export const event_types = {
EXTRAS_CONNECTED: 'extras_connected',
MESSAGE_SWIPED: 'message_swiped',
MESSAGE_SENT: 'message_sent',
MESSAGE_RECEIVED: 'message_received',
MESSAGE_EDITED: 'message_edited',
MESSAGE_DELETED: 'message_deleted',
IMPERSONATE_READY: 'impersonate_ready',
CHAT_CHANGED: 'chat_id_changed',
GENERATION_STOPPED: 'generation_stopped',
SETTINGS_UPDATED: 'settings_updated',
GROUP_UPDATED: 'group_updated',
MOVABLE_PANELS_RESET: 'movable_panels_reset',
}
export const eventSource = new EventEmitter();
$(document).ajaxError(function myErrorHandler(_, xhr) { $(document).ajaxError(function myErrorHandler(_, xhr) {
if (xhr.status == 403) { if (xhr.status == 403) {
toastr.warning("doubleCsrf errors in console are NORMAL in this case. Just reload the page or close this tab.", "Looks like you've opened SillyTavern in another browser tab", { timeOut: 0, extendedTimeOut: 0, preventDuplicates: true }); toastr.warning("doubleCsrf errors in console are NORMAL in this case. Just reload the page or close this tab.", "Looks like you've opened SillyTavern in another browser tab", { timeOut: 0, extendedTimeOut: 0, preventDuplicates: true });
@@ -1109,6 +1116,11 @@ function messageFormatting(mes, ch_name, isSystem, isUser) {
mes = mes.replaceAll(substituteParams(power_user.user_prompt_bias), ""); mes = mes.replaceAll(substituteParams(power_user.user_prompt_bias), "");
} }
const regexResult = getRegexedString(mes, regex_placement.MD_DISPLAY);
if (regexResult) {
mes = regexResult;
}
if (power_user.auto_fix_generated_markdown) { if (power_user.auto_fix_generated_markdown) {
mes = fixMarkdown(mes); mes = fixMarkdown(mes);
} }
@@ -1484,9 +1496,25 @@ function substituteParams(content, _name1, _name2, _original) {
content = content.replace(/<BOT>/gi, _name2); content = content.replace(/<BOT>/gi, _name2);
content = content.replace(/{{time}}/gi, moment().format('LT')); content = content.replace(/{{time}}/gi, moment().format('LT'));
content = content.replace(/{{date}}/gi, moment().format('LL')); content = content.replace(/{{date}}/gi, moment().format('LL'));
content = randomReplace(content);
return content; return content;
} }
function randomReplace(input, emptyListPlaceholder = '') {
const randomPattern = /{{random:([^}]+)}}/g;
return input.replace(randomPattern, (match, listString) => {
const list = listString.split(',').map(item => item.trim()).filter(item => item.length > 0);
if (list.length === 0) {
return emptyListPlaceholder;
}
const randomIndex = Math.floor(Math.random() * list.length);
return list[randomIndex];
});
}
function getStoppingStrings(isImpersonate, addSpace) { function getStoppingStrings(isImpersonate, addSpace) {
const charString = `\n${name2}:`; const charString = `\n${name2}:`;
const youString = `\nYou:`; const youString = `\nYou:`;
@@ -1588,7 +1616,7 @@ export function extractMessageBias(message) {
return null; return null;
} }
const forbiddenMatches = ['user', 'char', 'time', 'date']; const forbiddenMatches = ['user', 'char', 'time', 'date', 'random'];
const found = []; const found = [];
const rxp = /\{\{([\s\S]+?)\}\}/gm; const rxp = /\{\{([\s\S]+?)\}\}/gm;
//const rxp = /{([^}]+)}/g; //const rxp = /{([^}]+)}/g;
@@ -1597,7 +1625,12 @@ export function extractMessageBias(message) {
while ((curMatch = rxp.exec(message))) { while ((curMatch = rxp.exec(message))) {
const match = curMatch[1].trim(); const match = curMatch[1].trim();
if (forbiddenMatches.includes(match)) { // Ignore random pattern matches
if (/^random:.+/i.test(match)) {
continue;
}
if (forbiddenMatches.includes(match.toLowerCase())) {
continue; continue;
} }
@@ -2866,6 +2899,11 @@ export function replaceBiasMarkup(str) {
} }
export async function sendMessageAsUser(textareaText, messageBias) { export async function sendMessageAsUser(textareaText, messageBias) {
const regexResult = getRegexedString(textareaText, regex_placement.USER_INPUT);
if (regexResult) {
textareaText = regexResult;
}
chat[chat.length] = {}; chat[chat.length] = {};
chat[chat.length - 1]['name'] = name1; chat[chat.length - 1]['name'] = name1;
chat[chat.length - 1]['is_user'] = true; chat[chat.length - 1]['is_user'] = true;
@@ -3446,11 +3484,21 @@ function extractMessageFromData(data) {
} }
function cleanUpMessage(getMessage, isImpersonate, displayIncompleteSentences = false) { function cleanUpMessage(getMessage, isImpersonate, displayIncompleteSentences = false) {
// Append the user bias first before trimming anything else // Add the prompt bias before anything else
if (power_user.user_prompt_bias && power_user.user_prompt_bias.length !== 0) { if (
power_user.user_prompt_bias &&
!isImpersonate &&
power_user.user_prompt_bias.length !== 0
) {
getMessage = substituteParams(power_user.user_prompt_bias) + getMessage; getMessage = substituteParams(power_user.user_prompt_bias) + getMessage;
} }
// Regex uses vars, so add before formatting
const regexResult = getRegexedString(getMessage, isImpersonate ? regex_placement.USER_INPUT : regex_placement.AI_OUTPUT);
if (regexResult) {
getMessage = regexResult;
}
if (!displayIncompleteSentences && power_user.trim_sentences) { if (!displayIncompleteSentences && power_user.trim_sentences) {
getMessage = end_trim_to_sentence(getMessage, power_user.include_newline); getMessage = end_trim_to_sentence(getMessage, power_user.include_newline);
} }
@@ -3816,7 +3864,7 @@ async function renamePastChats(newAvatar, newValue) {
} }
} }
async function saveChat(chat_name, withMetadata) { async function saveChat(chat_name, withMetadata, mesId) {
const metadata = { ...chat_metadata, ...(withMetadata || {}) }; const metadata = { ...chat_metadata, ...(withMetadata || {}) };
let file_name = chat_name ?? characters[this_chid].chat; let file_name = chat_name ?? characters[this_chid].chat;
characters[this_chid]['date_last_chat'] = Date.now(); characters[this_chid]['date_last_chat'] = Date.now();
@@ -3837,6 +3885,11 @@ async function saveChat(chat_name, withMetadata) {
} }
*/ */
}); });
const trimmed_chat = (mesId !== undefined && mesId >= 0 && mesId < chat.length)
? chat.slice(0, parseInt(mesId) + 1)
: chat;
var save_chat = [ var save_chat = [
{ {
user_name: name1, user_name: name1,
@@ -3844,7 +3897,7 @@ async function saveChat(chat_name, withMetadata) {
create_date: chat_create_date, create_date: chat_create_date,
chat_metadata: metadata, chat_metadata: metadata,
}, },
...chat, ...trimmed_chat,
]; ];
return jQuery.ajax({ return jQuery.ajax({
type: "POST", type: "POST",
@@ -4768,10 +4821,13 @@ async function getSettings(type) {
if (data.enable_extensions) { if (data.enable_extensions) {
await loadExtensionSettings(settings); await loadExtensionSettings(settings);
eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED);
} }
} }
if (!is_checked_colab) isColab(); if (!is_checked_colab) isColab();
eventSource.emit(event_types.SETTINGS_LOADED);
} }
function selectKoboldGuiPreset() { function selectKoboldGuiPreset() {
@@ -4860,13 +4916,34 @@ function setCharacterBlockHeight() {
function updateMessage(div) { function updateMessage(div) {
const mesBlock = div.closest(".mes_block"); const mesBlock = div.closest(".mes_block");
let text = mesBlock.find(".edit_textarea").val(); let text = mesBlock.find(".edit_textarea").val();
const mes = chat[this_edit_mes_id];
let regexPlacement;
if (mes.is_name && !mes.is_user && mes.name !== name2) {
regexPlacement = regex_placement.SENDAS;
} else if (mes.is_name && !mes.is_user) {
regexPlacement = regex_placement.AI_OUTPUT;
} else if (mes.is_name && mes.is_user) {
regexPlacement = regex_placement.USER_INPUT;
} else if (mes.extra?.type === "narrator") {
regexPlacement = regex_placement.SYSTEM;
}
const regexResult = getRegexedString(
text,
regexPlacement,
{
characterOverride: regexPlacement === regex_placement.SENDAS ? mes.name : undefined
}
);
if (regexResult) {
text = regexResult;
}
if (power_user.trim_spaces) { if (power_user.trim_spaces) {
text = text.trim(); text = text.trim();
} }
const bias = extractMessageBias(text); const bias = extractMessageBias(text);
const mes = chat[this_edit_mes_id];
mes["mes"] = text; mes["mes"] = text;
if (mes["swipe_id"] !== undefined) { if (mes["swipe_id"] !== undefined) {
mes["swipes"][mes["swipe_id"]] = text; mes["swipes"][mes["swipe_id"]] = text;
@@ -5189,6 +5266,7 @@ export function select_selected_character(chid) {
$("#description_textarea").val(characters[chid].description); $("#description_textarea").val(characters[chid].description);
$("#character_world").val(characters[chid].data?.extensions?.world || ''); $("#character_world").val(characters[chid].data?.extensions?.world || '');
$("#creator_notes_textarea").val(characters[chid].data?.creator_notes || characters[chid].creatorcomment); $("#creator_notes_textarea").val(characters[chid].data?.creator_notes || characters[chid].creatorcomment);
$("#creator_notes_spoiler").text(characters[chid].data?.creator_notes || characters[chid].creatorcomment);
$("#character_version_textarea").val(characters[chid].data?.character_version || ''); $("#character_version_textarea").val(characters[chid].data?.character_version || '');
$("#system_prompt_textarea").val(characters[chid].data?.system_prompt || ''); $("#system_prompt_textarea").val(characters[chid].data?.system_prompt || '');
$("#post_history_instructions_textarea").val(characters[chid].data?.post_history_instructions || ''); $("#post_history_instructions_textarea").val(characters[chid].data?.post_history_instructions || '');
@@ -5257,6 +5335,7 @@ function select_rm_create() {
$("#description_textarea").val(create_save.description); $("#description_textarea").val(create_save.description);
$('#character_world').val(create_save.world); $('#character_world').val(create_save.world);
$("#creator_notes_textarea").val(create_save.creator_notes); $("#creator_notes_textarea").val(create_save.creator_notes);
$("#creator_notes_spoiler").text(create_save.creator_notes);
$("#post_history_instructions_textarea").val(create_save.post_history_instructions); $("#post_history_instructions_textarea").val(create_save.post_history_instructions);
$("#system_prompt_textarea").val(create_save.system_prompt); $("#system_prompt_textarea").val(create_save.system_prompt);
$("#tags_textarea").val(create_save.tags); $("#tags_textarea").val(create_save.tags);
@@ -5331,7 +5410,7 @@ function onScenarioOverrideRemoveClick() {
$(this).closest('.scenario_override').find('.chat_scenario').val('').trigger('input'); $(this).closest('.scenario_override').find('.chat_scenario').val('').trigger('input');
} }
function callPopup(text, type, inputValue = '') { function callPopup(text, type, inputValue = '', okButton) {
if (type) { if (type) {
popup_type = type; popup_type = type;
} }
@@ -5339,30 +5418,30 @@ function callPopup(text, type, inputValue = '') {
$("#dialogue_popup_cancel").css("display", "inline-block"); $("#dialogue_popup_cancel").css("display", "inline-block");
switch (popup_type) { switch (popup_type) {
case "avatarToCrop": case "avatarToCrop":
$("#dialogue_popup_ok").text("Accept"); $("#dialogue_popup_ok").text(okButton ?? "Accept");
break; break;
case "text": case "text":
case "alternate_greeting": case "alternate_greeting":
case "char_not_selected": case "char_not_selected":
$("#dialogue_popup_ok").text("Ok"); $("#dialogue_popup_ok").text(okButton ?? "Ok");
$("#dialogue_popup_cancel").css("display", "none"); $("#dialogue_popup_cancel").css("display", "none");
break; break;
case "new_chat": case "new_chat":
case "confirm": case "confirm":
$("#dialogue_popup_ok").text("Yes"); $("#dialogue_popup_ok").text(okButton ?? "Yes");
break; break;
case "del_group": case "del_group":
case "rename_chat": case "rename_chat":
case "del_chat": case "del_chat":
default: default:
$("#dialogue_popup_ok").text("Delete"); $("#dialogue_popup_ok").text(okButton ?? "Delete");
} }
$("#dialogue_popup_input").val(inputValue); $("#dialogue_popup_input").val(inputValue);
if (popup_type == 'input') { if (popup_type == 'input') {
$("#dialogue_popup_input").css("display", "block"); $("#dialogue_popup_input").css("display", "block");
$("#dialogue_popup_ok").text("Save"); $("#dialogue_popup_ok").text(okButton ?? "Save");
} }
else { else {
$("#dialogue_popup_input").css("display", "none"); $("#dialogue_popup_input").css("display", "none");
@@ -5960,9 +6039,11 @@ async function createOrEditCharacter(e) {
success: async function (html) { success: async function (html) {
if (chat.length === 1 && !selected_group) { if (chat.length === 1 && !selected_group) {
var this_ch_mes = default_ch_mes; var this_ch_mes = default_ch_mes;
if ($("#firstmessage_textarea").val() != "") { if ($("#firstmessage_textarea").val() != "") {
this_ch_mes = $("#firstmessage_textarea").val(); this_ch_mes = $("#firstmessage_textarea").val();
} }
if ( if (
this_ch_mes != this_ch_mes !=
$.trim( $.trim(
@@ -5973,6 +6054,13 @@ async function createOrEditCharacter(e) {
.text() .text()
) )
) { ) {
// MARK - kingbri: Regex on character greeting message
// May need to be placed somewhere else
const regexResult = getRegexedString(this_ch_mes, regex_placement.AI_OUTPUT);
if (regexResult) {
this_ch_mes = regexResult;
}
clearChat(); clearChat();
chat.length = 0; chat.length = 0;
chat[0] = {}; chat[0] = {};
@@ -7884,6 +7972,13 @@ $(document).ready(function () {
$("#load_select_chat_div").css("display", "block"); $("#load_select_chat_div").css("display", "block");
}); });
$(document).on("click", ".mes_create_bookmark", async function () {
var selected_mes_id = $(this).closest(".mes").attr("mesid");
if (selected_mes_id !== undefined) {
createNewBookmark(selected_mes_id);
}
});
$(document).on("click", ".mes_stop", function () { $(document).on("click", ".mes_stop", function () {
if (streamingProcessor) { if (streamingProcessor) {
streamingProcessor.abortController.abort(); streamingProcessor.abortController.abort();

View File

@@ -32,6 +32,7 @@ import {
} from "./utils.js"; } from "./utils.js";
export { export {
createNewBookmark,
showBookmarksButtons, showBookmarksButtons,
} }
@@ -123,13 +124,26 @@ function showBookmarksButtons() {
} }
} }
async function createNewBookmark() { async function saveBookmarkMenu() {
if (!chat.length) { if (!chat.length) {
toastr.warning('The chat is empty.', 'Bookmark creation failed'); toastr.warning('The chat is empty.', 'Bookmark creation failed');
return; return;
} }
const mesId = chat.length - 1; return createNewBookmark(chat.length - 1);
}
async function createNewBookmark(mesId) {
if (!chat.length) {
toastr.warning('The chat is empty.', 'Bookmark creation failed');
return;
}
if (mesId < 0 || mesId >= chat.length) {
toastr.warning('Invalid message ID.', 'Bookmark creation failed');
return;
}
const lastMes = chat[mesId]; const lastMes = chat[mesId];
if (typeof lastMes.extra !== 'object') { if (typeof lastMes.extra !== 'object') {
@@ -155,9 +169,9 @@ async function createNewBookmark() {
const newMetadata = { main_chat: mainChat }; const newMetadata = { main_chat: mainChat };
if (selected_group) { if (selected_group) {
await saveGroupBookmarkChat(selected_group, name, newMetadata); await saveGroupBookmarkChat(selected_group, name, newMetadata, mesId);
} else { } else {
await saveChat(name, newMetadata); await saveChat(name, newMetadata, mesId);
} }
lastMes.extra['bookmark_link'] = name; lastMes.extra['bookmark_link'] = name;
@@ -294,7 +308,7 @@ async function convertSoloToGroupChat() {
} }
$(document).ready(function () { $(document).ready(function () {
$('#option_new_bookmark').on('click', createNewBookmark); $('#option_new_bookmark').on('click', saveBookmarkMenu);
$('#option_back_to_main').on('click', backToMainChat); $('#option_back_to_main').on('click', backToMainChat);
$('#option_convert_to_group').on('click', convertSoloToGroupChat); $('#option_convert_to_group').on('click', convertSoloToGroupChat);
}); });

View File

@@ -56,6 +56,7 @@ const extension_settings = {
caption: {}, caption: {},
expressions: {}, expressions: {},
dice: {}, dice: {},
regex: [],
tts: {}, tts: {},
sd: {}, sd: {},
chromadb: {}, chromadb: {},
@@ -414,6 +415,7 @@ async function loadExtensionSettings(settings) {
$("#extensions_autoconnect").prop('checked', extension_settings.autoConnect); $("#extensions_autoconnect").prop('checked', extension_settings.autoConnect);
// Activate offline extensions // Activate offline extensions
eventSource.emit(event_types.EXTENSIONS_FIRST_LOAD);
extensionNames = await discoverExtensions(); extensionNames = await discoverExtensions();
manifests = await getManifests(extensionNames) manifests = await getManifests(extensionNames)
await activateExtensions(); await activateExtensions();

View File

@@ -9,7 +9,7 @@ import {
import { selected_group } from "../../group-chats.js"; import { selected_group } from "../../group-chats.js";
import { ModuleWorkerWrapper, extension_settings, getContext, saveMetadataDebounced } from "../../extensions.js"; import { ModuleWorkerWrapper, extension_settings, getContext, saveMetadataDebounced } from "../../extensions.js";
import { registerSlashCommand } from "../../slash-commands.js"; import { registerSlashCommand } from "../../slash-commands.js";
import { getCharaFilename, debounce } from "../../utils.js"; import { getCharaFilename, debounce, waitUntilCondition } from "../../utils.js";
export { MODULE_NAME as NOTE_MODULE_NAME }; export { MODULE_NAME as NOTE_MODULE_NAME };
const MODULE_NAME = '2_floating_prompt'; // <= Deliberate, for sorting lower than memory const MODULE_NAME = '2_floating_prompt'; // <= Deliberate, for sorting lower than memory
@@ -339,11 +339,7 @@ function onChatChanged() {
$('#extension_floating_default_token_counter').text(tokenCounter3); $('#extension_floating_default_token_counter').text(tokenCounter3);
} }
//for some reason exporting metadata_keys for WI usage caused this to throw errors function addExtensionsSettings() {
//"accessing eventSource before initialization"
//putting it on a 1ms Timeout solved this.
setTimeout(function () {
function addExtensionsSettings() {
const settingsHtml = ` const settingsHtml = `
<div id="floatingPrompt" class="drawer-content flexGap5"> <div id="floatingPrompt" class="drawer-content flexGap5">
<div class="panelControlBar flex-container"> <div class="panelControlBar flex-container">
@@ -444,6 +440,7 @@ setTimeout(function () {
<span data-i18n="Author's Note">Author's Note</span> <span data-i18n="Author's Note">Author's Note</span>
</a> </a>
`; `;
$('#options .options-content').prepend(ANButtonHtml); $('#options .options-content').prepend(ANButtonHtml);
$('#movingDivs').append(settingsHtml); $('#movingDivs').append(settingsHtml);
$('#extension_floating_prompt').on('input', onExtensionFloatingPromptInput); $('#extension_floating_prompt').on('input', onExtensionFloatingPromptInput);
@@ -463,12 +460,17 @@ setTimeout(function () {
setTimeout(function () { $('#floatingPrompt').hide() }, 200); setTimeout(function () { $('#floatingPrompt').hide() }, 200);
}) })
$("#option_toggle_AN").on('click', onANMenuItemClick); $("#option_toggle_AN").on('click', onANMenuItemClick);
} }
// Inject extension when extensions_activating is fired
// Inserts the extension first since it's statically imported
jQuery(async () => {
await waitUntilCondition(() => eventSource !== undefined);
eventSource.on(event_types.EXTENSIONS_FIRST_LOAD, addExtensionsSettings);
addExtensionsSettings();
registerSlashCommand('note', setNoteTextCommand, [], "<span class='monospace'>(text)</span> sets an author's note for the currently selected chat", true, true); registerSlashCommand('note', setNoteTextCommand, [], "<span class='monospace'>(text)</span> sets an author's note for the currently selected chat", true, true);
registerSlashCommand('depth', setNoteDepthCommand, [], "<span class='monospace'>(number)</span> sets an author's note depth for in-chat positioning", true, true); registerSlashCommand('depth', setNoteDepthCommand, [], "<span class='monospace'>(number)</span> sets an author's note depth for in-chat positioning", true, true);
registerSlashCommand('freq', setNoteIntervalCommand, ['interval'], "<span class='monospace'>(number)</span> sets an author's note insertion frequency", true, true); registerSlashCommand('freq', setNoteIntervalCommand, ['interval'], "<span class='monospace'>(number)</span> sets an author's note insertion frequency", true, true);
registerSlashCommand('pos', setNotePositionCommand, ['position'], "(<span class='monospace'>chat</span> or <span class='monospace'>scenario</span>) sets an author's note position", true, true); registerSlashCommand('pos', setNotePositionCommand, ['position'], "(<span class='monospace'>chat</span> or <span class='monospace'>scenario</span>) sets an author's note position", true, true);
eventSource.on(event_types.CHAT_CHANGED, onChatChanged); eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
}, 1); });

View File

@@ -0,0 +1,17 @@
<div class="regex_settings">
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<b>Regex</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<div id="open_regex_editor" class="menu_button">
<i class="fa-solid fa-pen-to-square"></i>
<span>Open Editor</span>
</div>
<hr />
<label>Saved Scripts</label>
<div id="saved_regex_scripts" class="flex-container regex-script-container flexFlowColumn"></div>
</div>
</div>
</div>

View File

@@ -0,0 +1,99 @@
<div id="regex_editor_template">
<div class="regex_editor">
<h3><strong>Regex Editor</strong></h3>
<div class="flex-container flexFlowColumn">
<div class="flex1">
<label for="regex_script_name" class="title_restorable">
<small data-i18n="Script Name">Script Name</small>
</label>
<div>
<input class="regex_script_name text_pole textarea_compact" type="text" />
</div>
</div>
<div class="flex1">
<label for="find_regex" class="title_restorable">
<small data-i18n="Find Regex">Find Regex</small>
</label>
<div>
<input class="find_regex text_pole textarea_compact" type="text" />
</div>
</div>
<div class="flex1">
<label for="regex_replace_string" class="title_restorable">
<small data-i18n="Replace With">Replace With</small>
</label>
<div>
<textarea
class="regex_replace_string text_pole wide100p textarea_compact"
placeholder="Use {{match}} to include the matched text from the Find Regex"
rows="2"
></textarea>
</div>
</div>
<div class="flex1">
<label for="regex_trim_strings" class="title_restorable">
<small data-i18n="Trim Out">Trim Out</small>
</label>
<div>
<textarea
class="regex_trim_strings text_pole wide100p textarea_compact"
placeholder="Globally trims any unwanted parts from a regex match before replacement. Separate each element by an enter."
rows="3"
></textarea>
</div>
</div>
</div>
<div class="flex-container">
<div class="wi-enter-footer-text flex-container flexFlowColumn flexNoGap alignitemsstart">
<small>Placement</small>
<div>
<label class="checkbox flex-container">
<input type="checkbox" name="replace_position" value="0">
<span data-i18n="Author's Note">Markdown Display</span>
</label>
</div>
<div>
<label class="checkbox flex-container">
<input type="checkbox" name="replace_position" value="1">
<span data-i18n="Before Char">User Input</span>
</label>
</div>
<div>
<label class="checkbox flex-container">
<input type="checkbox" name="replace_position" value="2">
<span data-i18n="After Char">AI Output</span>
</label>
</div>
<div>
<label class="checkbox flex-container">
<input type="checkbox" name="replace_position" value="3">
<span data-i18n="Author's Note">/sys Command</span>
</label>
</div>
<div>
<label class="checkbox flex-container">
<input type="checkbox" name="replace_position" value="4">
<span data-i18n="Author's Note">/sendas Command</span>
</label>
</div>
</div>
<div class="wi-enter-footer-text flex-container flexFlowColumn flexNoGap alignitemsstart">
<small>Other Options</small>
<label class="checkbox flex-container">
<input type="checkbox" name="disabled" />
<span data-i18n="Disabled">Disabled</span>
</label>
<label class="checkbox flex-container">
<input type="checkbox" name="run_on_edit" />
<span data-i18n="Run On Edit">Run On Edit</span>
</label>
<label class="checkbox flex-container">
<input type="checkbox" name="substitute_regex" />
<span data-i18n="Substitute Regex">Substitute Regex</span>
</label>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,105 @@
import { substituteParams } from "../../../script.js";
import { extension_settings } from "../../extensions.js";
export {
regex_placement,
getRegexedString,
runRegexScript
}
const regex_placement = {
MD_DISPLAY: 0,
USER_INPUT: 1,
AI_OUTPUT: 2,
SYSTEM: 3,
SENDAS: 4
}
// From: https://github.com/IonicaBizau/regex-parser.js/blob/master/lib/index.js
function regexFromString(input) {
// Parse input
var m = input.match(/(\/?)(.+)\1([a-z]*)/i);
// Invalid flags
if (m[3] && !/^(?!.*?(.).*?\1)[gmixXsuUAJ]+$/.test(m[3])) {
return RegExp(input);
}
// Create the regular expression
return new RegExp(m[2], m[3]);
}
function getRegexedString(rawString, placement, { characterOverride } = {}) {
if (extension_settings.disabledExtensions.includes("regex") || !rawString || placement === undefined) {
return;
}
let finalString;
extension_settings.regex.forEach((script) => {
if (script.placement.includes(placement)) {
finalString = runRegexScript(script, rawString, { characterOverride });
}
});
return finalString;
}
// Runs the provided regex script on the given string
function runRegexScript(regexScript, rawString, { characterOverride } = {}) {
if (!regexScript || !!(regexScript.disabled) || !regexScript?.findRegex || !rawString) {
return;
}
let match;
let newString;
const findRegex = regexFromString(regexScript.substituteRegex ? substituteParams(regexScript.findRegex) : regexScript.findRegex);
while ((match = findRegex.exec(rawString)) !== null) {
const fencedMatch = match[0];
const capturedMatch = match[1];
let trimCapturedMatch;
let trimFencedMatch;
if (capturedMatch) {
const tempTrimCapture = filterString(capturedMatch, regexScript.trimStrings, { characterOverride });
trimFencedMatch = fencedMatch.replaceAll(capturedMatch, tempTrimCapture);
trimCapturedMatch = tempTrimCapture;
} else {
trimFencedMatch = filterString(fencedMatch, regexScript.trimStrings, { characterOverride });
}
// TODO: Use substrings for replacement. But not necessary at this time.
// A substring is from match.index to match.index + match[0].length or fencedMatch.length
const subReplaceString = substituteRegexParams(regexScript.replaceString, trimCapturedMatch ?? trimFencedMatch, { characterOverride });
if (!newString) {
newString = rawString.replace(fencedMatch, subReplaceString);
} else {
newString = newString.replace(fencedMatch, subReplaceString);
}
// If the regex isn't global, break out of the loop
if (!findRegex.flags.includes('g')) {
break;
}
}
return newString;
}
// Filters anything to trim from the regex match
function filterString(rawString, trimStrings, { characterOverride } = {}) {
let finalString = rawString;
trimStrings.forEach((trimString) => {
const subTrimString = substituteParams(trimString, undefined, characterOverride);
finalString = finalString.replaceAll(subTrimString, "");
});
return finalString;
}
// Substitutes regex-specific and normal parameters
function substituteRegexParams(rawString, regexMatch, { characterOverride } = {}) {
let finalString = rawString;
finalString = finalString.replace("{{match}}", regexMatch);
finalString = substituteParams(finalString, undefined, characterOverride);
return finalString;
}

View File

@@ -0,0 +1,184 @@
import { callPopup, eventSource, event_types, reloadCurrentChat, saveSettingsDebounced } from "../../../script.js";
import { extension_settings } from "../../extensions.js";
import { uuidv4, waitUntilCondition } from "../../utils.js";
import { regex_placement } from "./engine.js";
async function saveRegexScript(regexScript, existingScriptIndex) {
// If not editing
if (existingScriptIndex === -1) {
// Is the script name undefined?
if (!regexScript.scriptName) {
toastr.error(`Could not save regex script: The script name was undefined or empty!`);
return;
}
// Does the script name already exist?
if (extension_settings.regex.find((e) => e.scriptName === regexScript.scriptName)) {
toastr.error(`Could not save regex script: A script with name ${regexScript.scriptName} already exists.`);
return;
}
} else {
// Does the script name already exist somewhere else?
// (If this fails, make it a .filter().map() to index array)
const foundIndex = extension_settings.regex.findIndex((e) => e.scriptName === regexScript.scriptName);
if (foundIndex !== existingScriptIndex && foundIndex !== -1) {
toastr.error(`Could not save regex script: A script with name ${regexScript.scriptName} already exists.`);
return;
}
}
// Is a find regex present?
if (regexScript.findRegex.length === 0) {
toastr.error(`Could not save regex script: A find regex is required!`);
return;
}
// Is there someplace to place results?
if (regexScript.placement.length === 0) {
toastr.error(`Could not save regex script: One placement checkbox must be selected!`);
return;
}
if (existingScriptIndex !== -1) {
extension_settings.regex[existingScriptIndex] = regexScript;
} else {
extension_settings.regex.push(regexScript);
}
saveSettingsDebounced();
await loadRegexScripts();
// Markdown is global, so reload the chat.
if (regexScript.placement.includes(regex_placement.MD_DISPLAY)) {
await reloadCurrentChat();
}
}
async function deleteRegexScript({ existingId }) {
let scriptName = $(`#${existingId}`).find('.regex_script_name').text();
const existingScriptIndex = extension_settings.regex.findIndex((script) => script.scriptName === scriptName);
if (!existingScriptIndex || existingScriptIndex !== -1) {
extension_settings.regex.splice(existingScriptIndex, 1);
saveSettingsDebounced();
await loadRegexScripts();
}
}
async function loadRegexScripts() {
$("#saved_regex_scripts").empty();
const scriptTemplate = $(await $.get("scripts/extensions/regex/scriptTemplate.html"));
extension_settings.regex.forEach((script) => {
// Have to clone here
const scriptHtml = scriptTemplate.clone();
scriptHtml.attr('id', uuidv4());
scriptHtml.find('.regex_script_name').text(script.scriptName);
scriptHtml.find('.edit_existing_regex').on('click', async function() {
await onRegexEditorOpenClick(scriptHtml.attr("id"));
});
scriptHtml.find('.delete_regex').on('click', async function() {
await deleteRegexScript({ existingId: scriptHtml.attr("id") });
});
$("#saved_regex_scripts").append(scriptHtml);
});
}
async function onRegexEditorOpenClick(existingId) {
const editorHtml = $(await $.get("scripts/extensions/regex/editor.html"));
// If an ID exists, fill in all the values
let existingScriptIndex = -1;
if (existingId) {
const existingScriptName = $(`#${existingId}`).find('.regex_script_name').text();
existingScriptIndex = extension_settings.regex.findIndex((script) => script.scriptName === existingScriptName);
if (existingScriptIndex !== -1) {
const existingScript = extension_settings.regex[existingScriptIndex];
if (existingScript.scriptName) {
editorHtml.find(`.regex_script_name`).val(existingScript.scriptName);
} else {
toastr.error("This script doesn't have a name! Please delete it.")
return;
}
editorHtml.find(`.find_regex`).val(existingScript.findRegex || "");
editorHtml.find(`.regex_replace_string`).val(existingScript.replaceString || "");
editorHtml.find(`.regex_trim_strings`).val(existingScript.trimStrings?.join("\n") || []);
editorHtml
.find(`input[name="disabled"]`)
.prop("checked", existingScript.disabled ?? false);
editorHtml
.find(`input[name="run_on_edit"]`)
.prop("checked", existingScript.runOnEdit ?? false);
editorHtml
.find(`input[name="substitute_regex"]`)
.prop("checked", existingScript.substituteRegex ?? false);
existingScript.placement.forEach((element) => {
editorHtml
.find(`input[name="replace_position"][value="${element}"]`)
.prop("checked", true);
});
}
} else {
editorHtml
.find(`input[name="run_on_edit"]`)
.prop("checked", true);
editorHtml
.find(`input[name="replace_position"][value="0"]`)
.prop("checked", true);
}
const popupResult = await callPopup(editorHtml, "confirm", undefined, "Save");
if (popupResult) {
const newRegexScript = {
scriptName: editorHtml.find(".regex_script_name").val(),
findRegex: editorHtml.find(".find_regex").val(),
replaceString: editorHtml.find(".regex_replace_string").val(),
trimStrings: editorHtml.find(".regex_trim_strings").val().split("\n").filter((e) => e.length !== 0) || [],
placement:
editorHtml
.find(`input[name="replace_position"]`)
.filter(":checked")
.map(function() { return parseInt($(this).val()) })
.get()
.filter((e) => e !== NaN) || [],
disabled:
editorHtml
.find(`input[name="disabled"]`)
.prop("checked"),
runOnEdit:
editorHtml
.find(`input[name="run_on_edit"]`)
.prop("checked"),
substituteRegex:
editorHtml
.find(`input[name="substitute_regex"]`)
.prop("checked")
};
saveRegexScript(newRegexScript, existingScriptIndex);
}
}
// Workaround for loading in sequence with other extensions
// NOTE: Always puts extension at the top of the list, but this is fine since it's static
jQuery(async () => {
console.log("REGEX CALLED")
// Manually disable the extension since static imports auto-import the JS file
if (extension_settings.disabledExtensions.includes("regex")) {
return;
}
const settingsHtml = await $.get("scripts/extensions/regex/dropdown.html");
$("#extensions_settings2").append(settingsHtml);
$("#open_regex_editor").on("click", function() {
onRegexEditorOpenClick(false);
});
await loadRegexScripts();
});

View File

@@ -0,0 +1,11 @@
{
"display_name": "Regex",
"loading_order": 1,
"requires": [],
"optional": [],
"js": "index.js",
"css": "style.css",
"author": "kingbri",
"version": "1.0.0",
"homePage": "https://github.com/SillyTavern/SillyTavern"
}

View File

@@ -0,0 +1,11 @@
<div class="regex-script-label flex-container flexnowrap">
<div class="regex_script_name flexGrow overflow-hidden"></div>
<div class="flex-container flexnowrap">
<div class="edit_existing_regex menu_button">
<i class="fa-solid fa-pencil"></i>
</div>
<div class="delete_regex menu_button">
<i class="fa-solid fa-trash"></i>
</div>
</div>
</div>

View File

@@ -0,0 +1,20 @@
.regex_settings .menu_button {
width: fit-content;
display: flex;
gap: 10px;
flex-direction: row;
}
.regex-script-container {
margin-top: 10px;
margin-bottom: 10px;
}
.regex-script-label {
align-items: center;
border: 1px solid rgba(128, 128, 128, 0.5);
border-radius: 10px;
padding: 0 5px;
margin-top: 1px;
margin-bottom: 1px;
}

View File

@@ -1440,7 +1440,7 @@ export async function importGroupChat(formData) {
}); });
} }
export async function saveGroupBookmarkChat(groupId, name, metadata) { export async function saveGroupBookmarkChat(groupId, name, metadata, mesId) {
const group = groups.find(x => x.id === groupId); const group = groups.find(x => x.id === groupId);
if (!group) { if (!group) {
@@ -1450,12 +1450,16 @@ export async function saveGroupBookmarkChat(groupId, name, metadata) {
group.past_metadata[name] = { ...chat_metadata, ...(metadata || {}) }; group.past_metadata[name] = { ...chat_metadata, ...(metadata || {}) };
group.chats.push(name); group.chats.push(name);
const trimmed_chat = (mesId !== undefined && mesId >= 0 && mesId < chat.length)
? chat.slice(0, parseInt(mesId) + 1)
: chat;
await editGroup(groupId, true); await editGroup(groupId, true);
await fetch("/savegroupchat", { await fetch("/savegroupchat", {
method: "POST", method: "POST",
headers: getRequestHeaders(), headers: getRequestHeaders(),
body: JSON.stringify({ id: name, chat: [...chat] }), body: JSON.stringify({ id: name, chat: [...trimmed_chat] }),
}); });
} }

View File

@@ -316,6 +316,32 @@ function switchWaifuMode() {
scrollChatToBottom(); scrollChatToBottom();
} }
function switchSpoilerMode() {
if (power_user.spoiler_free_mode) {
$("#description_div").hide();
$("#description_textarea").hide();
$("#firstmessage_textarea").hide();
$("#first_message_div").hide();
$("#spoiler_free_desc").show();
}
else {
$("#description_div").show();
$("#description_textarea").show();
$("#firstmessage_textarea").show();
$("#first_message_div").show();
$("#spoiler_free_desc").hide();
}
}
function peekSpoilerMode() {
$("#description_div").toggle();
$("#description_textarea").toggle();
$("#firstmessage_textarea").toggle();
$("#first_message_div").toggle();
}
function switchMovingUI() { function switchMovingUI() {
const movingUI = localStorage.getItem(storage_keys.movingUI); const movingUI = localStorage.getItem(storage_keys.movingUI);
power_user.movingUI = movingUI === null ? false : movingUI == "true"; power_user.movingUI = movingUI === null ? false : movingUI == "true";
@@ -632,6 +658,7 @@ function loadPowerUserSettings(settings, data) {
$(`#pygmalion_formatting option[value=${power_user.pygmalion_formatting}]`).attr("selected", true); $(`#pygmalion_formatting option[value=${power_user.pygmalion_formatting}]`).attr("selected", true);
$(`#send_on_enter option[value=${power_user.send_on_enter}]`).attr("selected", true); $(`#send_on_enter option[value=${power_user.send_on_enter}]`).attr("selected", true);
$("#import_card_tags").prop("checked", power_user.import_card_tags); $("#import_card_tags").prop("checked", power_user.import_card_tags);
$("#spoiler_free_mode").prop("checked", power_user.spoiler_free_mode);
$("#collapse-newlines-checkbox").prop("checked", power_user.collapse_newlines); $("#collapse-newlines-checkbox").prop("checked", power_user.collapse_newlines);
$("#pin-examples-checkbox").prop("checked", power_user.pin_examples); $("#pin-examples-checkbox").prop("checked", power_user.pin_examples);
$("#disable-description-formatting-checkbox").prop("checked", power_user.disable_description_formatting); $("#disable-description-formatting-checkbox").prop("checked", power_user.disable_description_formatting);
@@ -705,6 +732,7 @@ function loadPowerUserSettings(settings, data) {
loadInstructMode(); loadInstructMode();
loadMaxContextUnlocked(); loadMaxContextUnlocked();
switchWaifuMode(); switchWaifuMode();
switchSpoilerMode();
loadMovingUIState(); loadMovingUIState();
//console.log(power_user) //console.log(power_user)
@@ -1786,6 +1814,17 @@ $(document).ready(() => {
saveSettingsDebounced(); saveSettingsDebounced();
}); });
$('#spoiler_free_mode').on('input', function () {
power_user.spoiler_free_mode = !!$(this).prop('checked');
switchSpoilerMode();
saveSettingsDebounced();
});
$('#spoiler_free_desc_button').on('click', function () {
peekSpoilerMode();
$(this).toggleClass('fa-eye fa-eye-slash');
});
$(window).on('focus', function () { $(window).on('focus', function () {
browser_has_focus = true; browser_has_focus = true;
}); });

View File

@@ -22,6 +22,7 @@ import {
} from "../script.js"; } from "../script.js";
import { humanizedDateTime } from "./RossAscends-mods.js"; import { humanizedDateTime } from "./RossAscends-mods.js";
import { resetSelectedGroup } from "./group-chats.js"; import { resetSelectedGroup } from "./group-chats.js";
import { getRegexedString, regex_placement } from "./extensions/regex/engine.js";
import { chat_styles, power_user } from "./power-user.js"; import { chat_styles, power_user } from "./power-user.js";
export { export {
executeSlashCommands, executeSlashCommands,
@@ -218,14 +219,18 @@ async function sendMessageAs(_, text) {
} }
const parts = text.split('\n'); const parts = text.split('\n');
if (parts.length <= 1) { if (parts.length <= 1) {
toastr.warning('Both character name and message are required. Separate them with a new line.'); toastr.warning('Both character name and message are required. Separate them with a new line.');
return; return;
} }
const name = parts.shift().trim(); const name = parts.shift().trim();
const mesText = parts.join('\n').trim(); let mesText = parts.join('\n').trim();
const regexResult = getRegexedString(mesText, regex_placement.SENDAS, { characterOverride: name });
if (regexResult) {
mesText = regexResult;
}
// Messages that do nothing but set bias will be hidden from the context // Messages that do nothing but set bias will be hidden from the context
const bias = extractMessageBias(mesText); const bias = extractMessageBias(mesText);
const isSystem = replaceBiasMarkup(mesText).trim().length === 0; const isSystem = replaceBiasMarkup(mesText).trim().length === 0;
@@ -268,6 +273,11 @@ async function sendNarratorMessage(_, text) {
return; return;
} }
const regexResult = getRegexedString(text, regex_placement.SYSTEM);
if (regexResult) {
text = regexResult;
}
const name = chat_metadata[NARRATOR_NAME_KEY] || NARRATOR_NAME_DEFAULT; const name = chat_metadata[NARRATOR_NAME_KEY] || NARRATOR_NAME_DEFAULT;
// Messages that do nothing but set bias will be hidden from the context // Messages that do nothing but set bias will be hidden from the context
const bias = extractMessageBias(text); const bias = extractMessageBias(text);

View File

@@ -2478,6 +2478,7 @@ input[type="range"]::-webkit-slider-thumb {
.mes_buttons .mes_edit, .mes_buttons .mes_edit,
.mes_buttons .mes_bookmark, .mes_buttons .mes_bookmark,
.mes_buttons .mes_create_bookmark,
.extraMesButtonsHint, .extraMesButtonsHint,
.tagListHint, .tagListHint,
.extraMesButtons div { .extraMesButtons div {
@@ -2489,6 +2490,7 @@ input[type="range"]::-webkit-slider-thumb {
.mes_buttons .mes_edit:hover, .mes_buttons .mes_edit:hover,
.mes_buttons .mes_bookmark:hover, .mes_buttons .mes_bookmark:hover,
.mes_buttons .mes_create_bookmark:hover,
.extraMesButtonsHint:hover, .extraMesButtonsHint:hover,
.tagListHint:hover, .tagListHint:hover,
.extraMesButtons div:hover { .extraMesButtons div:hover {
@@ -4005,6 +4007,16 @@ toolcool-color-picker {
align-items: center; align-items: center;
} }
.alignitemsstart {
align-items: start;
}
.overflow-hidden {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.padding5 { .padding5 {
padding: 5px; padding: 5px;
} }
@@ -4068,6 +4080,10 @@ toolcool-color-picker {
justify-content: space-around; justify-content: space-around;
} }
.justifyContentFlexStart {
justify-content: flex-start;
}
.justifyContentFlexEnd { .justifyContentFlexEnd {
justify-content: flex-end; justify-content: flex-end;
} }
@@ -4095,6 +4111,10 @@ toolcool-color-picker {
width: 100%; width: 100%;
} }
.wide50p {
width: 50%;
}
.wide50px { .wide50px {
width: 50px; width: 50px;
} }