mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge pull request #630 from bdashore3/dev
Add regex engine and other fixes
This commit is contained in:
@@ -2428,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>
|
||||||
|
109
public/script.js
109
public/script.js
@@ -165,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 {
|
||||||
@@ -241,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 } });
|
||||||
|
|
||||||
@@ -482,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 });
|
||||||
@@ -1112,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);
|
||||||
}
|
}
|
||||||
@@ -2869,6 +2878,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;
|
||||||
@@ -3449,11 +3463,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);
|
||||||
}
|
}
|
||||||
@@ -4776,10 +4800,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() {
|
||||||
@@ -4868,13 +4895,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;
|
||||||
@@ -5341,7 +5389,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;
|
||||||
}
|
}
|
||||||
@@ -5349,30 +5397,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");
|
||||||
@@ -5970,9 +6018,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(
|
||||||
@@ -5983,6 +6033,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] = {};
|
||||||
|
@@ -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();
|
||||||
|
@@ -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);
|
});
|
||||||
|
17
public/scripts/extensions/regex/dropdown.html
Normal file
17
public/scripts/extensions/regex/dropdown.html
Normal 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>
|
99
public/scripts/extensions/regex/editor.html
Normal file
99
public/scripts/extensions/regex/editor.html
Normal 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>
|
105
public/scripts/extensions/regex/engine.js
Normal file
105
public/scripts/extensions/regex/engine.js
Normal 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;
|
||||||
|
}
|
184
public/scripts/extensions/regex/index.js
Normal file
184
public/scripts/extensions/regex/index.js
Normal 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();
|
||||||
|
});
|
11
public/scripts/extensions/regex/manifest.json
Normal file
11
public/scripts/extensions/regex/manifest.json
Normal 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"
|
||||||
|
}
|
11
public/scripts/extensions/regex/scriptTemplate.html
Normal file
11
public/scripts/extensions/regex/scriptTemplate.html
Normal 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>
|
20
public/scripts/extensions/regex/style.css
Normal file
20
public/scripts/extensions/regex/style.css
Normal 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;
|
||||||
|
}
|
@@ -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);
|
||||||
|
@@ -3999,6 +3999,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;
|
||||||
}
|
}
|
||||||
@@ -4062,6 +4072,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;
|
||||||
}
|
}
|
||||||
@@ -4089,6 +4103,10 @@ toolcool-color-picker {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wide50p {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
.wide50px {
|
.wide50px {
|
||||||
width: 50px;
|
width: 50px;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user