Merge branch 'staging' of https://github.com/SillyTavern/SillyTavern into staging

This commit is contained in:
Cohee
2023-11-11 18:25:47 +02:00
11 changed files with 195 additions and 74 deletions

View File

@ -163,6 +163,8 @@
"custom_stopping_strings_macro": true, "custom_stopping_strings_macro": true,
"fuzzy_search": true, "fuzzy_search": true,
"encode_tags": false, "encode_tags": false,
"enableLabMode": false,
"enableZenSliders": false,
"ui_mode": 1 "ui_mode": 1
}, },
"extension_settings": { "extension_settings": {

View File

@ -13,7 +13,7 @@
.tag_view_item { .tag_view_item {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: baseline; align-items: center;
gap: 10px; gap: 10px;
margin-bottom: 5px; margin-bottom: 5px;
} }

View File

@ -715,8 +715,8 @@
Temperature Temperature
<div class="fa-solid fa-circle-info opacity50p" title="Temperature controls the randomness in token selection:&#13;- low temperature (<1.0) leads to more predictable text, favoring higher probability tokens.&#13;- high temperature (>1.0) increases creativity and diversity in the output by giving lower probability tokens a better chance.&#13;Set to 1.0 for the original probabilities."></div> <div class="fa-solid fa-circle-info opacity50p" title="Temperature controls the randomness in token selection:&#13;- low temperature (<1.0) leads to more predictable text, favoring higher probability tokens.&#13;- high temperature (>1.0) increases creativity and diversity in the output by giving lower probability tokens a better chance.&#13;Set to 1.0 for the original probabilities."></div>
</small> </small>
<input class="neo-range-slider" type="range" id="temp" name="volume" min="0.0" max="2.0" step="0.01"> <input class="neo-range-slider" type="range" id="temp" name="volume" min="0.0" max="4.0" step="0.01">
<input class="neo-range-input" type="number" min="0.0" max="2.0" step="0.01" data-for="temp" id="temp_counter"> <input class="neo-range-input" type="number" min="0.0" max="4.0" step="0.01" data-for="temp" id="temp_counter">
</div> </div>
<div data-newbie-hidden class="alignitemscenter flex-container flexFlowColumn flexBasis48p flexGrow flexShrink gap0"> <div data-newbie-hidden class="alignitemscenter flex-container flexFlowColumn flexBasis48p flexGrow flexShrink gap0">
<small data-i18n="Top K"> <small data-i18n="Top K">
@ -3095,7 +3095,7 @@
</div> </div>
<div id="create_dummy_persona" class="menu_button menu_button_icon" title="Create a dummy persona" data-i18n="[title]Create a dummy persona"> <div id="create_dummy_persona" class="menu_button menu_button_icon" title="Create a dummy persona" data-i18n="[title]Create a dummy persona">
<i class="fa-solid fa-person-circle-question fa-fw"></i> <i class="fa-solid fa-person-circle-question fa-fw"></i>
<span data-i18n="Blank">Blank</span> <span data-i18n="Create">Create</span>
</div> </div>
</h4> </h4>
<div id="user_avatar_block"> <div id="user_avatar_block">
@ -4174,7 +4174,7 @@
<div id="bogus_folder_template" class="template_element"> <div id="bogus_folder_template" class="template_element">
<div class="bogus_folder_select flex-container wide100p alignitemsflexstart"> <div class="bogus_folder_select flex-container wide100p alignitemsflexstart">
<div class="avatar flex alignitemscenter textAlignCenter"> <div class="avatar flex alignitemscenter textAlignCenter">
<i class="fa-solid fa-folder-open fa-xl"></i> <i class="bogus_folder_icon fa-solid fa-folder-open fa-xl"></i>
</div> </div>
<div class="flex-container wide100pLess70px character_select_container"> <div class="flex-container wide100pLess70px character_select_container">
<div class="wide100p character_name_block"> <div class="wide100p character_name_block">
@ -4187,6 +4187,18 @@
</div> </div>
</div> </div>
</div> </div>
<div id="bogus_folder_back_template" class="template_element">
<div class="bogus_folder_select flex-container wide100p alignitemsflexstart" id="BogusFolderBack" tagid="back">
<div class="avatar flex alignitemscenter textAlignCenter">
<i class="bogus_folder_icon fa-solid fa-xl fa-right-from-bracket fa-flip-horizontal"></i>
</div>
<div class="flex-container wide100pLess70px character_select_container height100p alignitemscenter">
<div class="wide100p character_name_block">
<span class="ch_name">Go back</span>
</div>
</div>
</div>
</div>
<div id="hotswap_template" class="template_element"> <div id="hotswap_template" class="template_element">
<div class="hotswapAvatar" title="Add a character/group to favorites to display it here!"> <div class="hotswapAvatar" title="Add a character/group to favorites to display it here!">
<img src="/img/ai4.png"> <img src="/img/ai4.png">

View File

@ -989,16 +989,28 @@ export async function selectCharacterById(id) {
} }
} }
function getTagBlock(item) { function getTagBlock(item, entities) {
const count = Object.values(tag_map).flat().filter(x => x == item.id).length; let count = 0;
for (const entity of entities) {
if (entitiesFilter.isElementTagged(entity, item.id)) {
count++;
}
}
const template = $('#bogus_folder_template .bogus_folder_select').clone(); const template = $('#bogus_folder_template .bogus_folder_select').clone();
template.attr({ 'tagid': item.id, 'id': `BogusFolder${item.id}` }); template.attr({ 'tagid': item.id, 'id': `BogusFolder${item.id}` });
template.find('.avatar').css({'background-color': item.color, 'color': item.color2 }); template.find('.avatar').css({ 'background-color': item.color, 'color': item.color2 });
template.find('.ch_name').text(item.name); template.find('.ch_name').text(item.name);
template.find('.bogus_folder_counter').text(count); template.find('.bogus_folder_counter').text(count);
return template; return template;
} }
function getBackBlock() {
const template = $('#bogus_folder_back_template .bogus_folder_select').clone();
return template;
}
function getEmptyBlock() { function getEmptyBlock() {
const icons = ['fa-dragon', 'fa-otter', 'fa-kiwi-bird', 'fa-crow', 'fa-frog']; const icons = ['fa-dragon', 'fa-otter', 'fa-kiwi-bird', 'fa-crow', 'fa-frog'];
const texts = ['Here be dragons', 'Otterly empty', 'Kiwibunga', 'Pump-a-Rum', 'Croak it']; const texts = ['Here be dragons', 'Otterly empty', 'Kiwibunga', 'Pump-a-Rum', 'Croak it'];
@ -1061,10 +1073,9 @@ async function printCharacters(fullRefresh = false) {
saveCharactersPage = 0; saveCharactersPage = 0;
printTagFilters(tag_filter_types.character); printTagFilters(tag_filter_types.character);
printTagFilters(tag_filter_types.group_member); printTagFilters(tag_filter_types.group_member);
const isBogusFolderOpen = !!entitiesFilter.getFilterData(FILTER_TYPES.TAG)?.bogus;
// Return to main list // Return to main list
if (isBogusFolderOpen) { if (isBogusFolderOpen()) {
entitiesFilter.setFilterData(FILTER_TYPES.TAG, { excluded: [], selected: [] }); entitiesFilter.setFilterData(FILTER_TYPES.TAG, { excluded: [], selected: [] });
} }
@ -1073,8 +1084,11 @@ async function printCharacters(fullRefresh = false) {
} }
const storageKey = 'Characters_PerPage'; const storageKey = 'Characters_PerPage';
const listId = '#rm_print_characters_block';
const entities = getEntitiesList({ doFilter: true });
$("#rm_print_characters_pagination").pagination({ $("#rm_print_characters_pagination").pagination({
dataSource: getEntitiesList({ doFilter: true }), dataSource: entities,
pageSize: Number(localStorage.getItem(storageKey)) || per_page_default, pageSize: Number(localStorage.getItem(storageKey)) || per_page_default,
sizeChangerOptions: [10, 25, 50, 100, 250, 500, 1000], sizeChangerOptions: [10, 25, 50, 100, 250, 500, 1000],
pageRange: 1, pageRange: 1,
@ -1087,20 +1101,25 @@ async function printCharacters(fullRefresh = false) {
formatNavigator: PAGINATION_TEMPLATE, formatNavigator: PAGINATION_TEMPLATE,
showNavigator: true, showNavigator: true,
callback: function (data) { callback: function (data) {
$("#rm_print_characters_block").empty(); $(listId).empty();
for (const i of data) { if (isBogusFolderOpen()) {
if (i.type === 'character') { $(listId).append(getBackBlock());
$("#rm_print_characters_block").append(getCharacterBlock(i.item, i.id));
}
if (i.type === 'group') {
$("#rm_print_characters_block").append(getGroupBlock(i.item));
}
if (i.type === 'tag') {
$("#rm_print_characters_block").append(getTagBlock(i.item));
}
} }
if (!data.length) { if (!data.length) {
$("#rm_print_characters_block").append(getEmptyBlock()); $(listId).append(getEmptyBlock());
}
for (const i of data) {
switch (i.type) {
case 'character':
$(listId).append(getCharacterBlock(i.item, i.id));
break;
case 'group':
$(listId).append(getGroupBlock(i.item));
break;
case 'tag':
$(listId).append(getTagBlock(i.item, entities));
break;
}
} }
eventSource.emit(event_types.CHARACTER_PAGE_LOADED); eventSource.emit(event_types.CHARACTER_PAGE_LOADED);
}, },
@ -1111,26 +1130,60 @@ async function printCharacters(fullRefresh = false) {
saveCharactersPage = e; saveCharactersPage = e;
}, },
afterRender: function () { afterRender: function () {
$('#rm_print_characters_block').scrollTop(0); $(listId).scrollTop(0);
}, },
}); });
favsToHotswap(); favsToHotswap();
} }
export function getEntitiesList({ doFilter } = {}) { /**
let entities = []; * Indicates whether a user is currently in a bogus folder.
entities.push(...characters.map((item, index) => ({ item, id: index, type: 'character' }))); * @returns {boolean} If currently viewing a folder
entities.push(...groups.map((item) => ({ item, id: item.id, type: 'group' }))); */
function isBogusFolderOpen() {
return !!entitiesFilter.getFilterData(FILTER_TYPES.TAG)?.bogus;
}
if (power_user.bogus_folders) { export function getEntitiesList({ doFilter } = {}) {
entities.push(...tags.map((item) => ({ item, id: item.id, type: 'tag' }))); function characterToEntity(character, id) {
return { item: character, id, type: 'character' };
} }
function groupToEntity(group) {
return { item: group, id: group.id, type: 'group' };
}
function tagToEntity(tag) {
return { item: structuredClone(tag), id: tag.id, type: 'tag' };
}
let entities = [
...characters.map((item, index) => characterToEntity(item, index)),
...groups.map(item => groupToEntity(item)),
...(power_user.bogus_folders ? tags.map(item => tagToEntity(item)) : []),
];
if (doFilter) { if (doFilter) {
entities = entitiesFilter.applyFilters(entities); entities = entitiesFilter.applyFilters(entities);
} }
if (isBogusFolderOpen()) {
// Get tags of entities within the bogus folder
const filterData = structuredClone(entitiesFilter.getFilterData(FILTER_TYPES.TAG));
entities = entities.filter(x => x.type !== 'tag');
const otherTags = tags.filter(x => !filterData.selected.includes(x.id));
const bogusTags = [];
for (const entity of entities) {
for (const tag of otherTags) {
if (!bogusTags.includes(tag) && entitiesFilter.isElementTagged(entity, tag.id)) {
bogusTags.push(tag);
}
}
}
entities.push(...bogusTags.map(item => tagToEntity(item)));
}
sortEntitiesList(entities); sortEntitiesList(entities);
return entities; return entities;
} }
@ -7524,8 +7577,25 @@ jQuery(async function () {
$(document).on("click", ".bogus_folder_select", function () { $(document).on("click", ".bogus_folder_select", function () {
const tagId = $(this).attr('tagid'); const tagId = $(this).attr('tagid');
console.log('Bogus folder clicked', tagId); console.log('Bogus folder clicked', tagId);
entitiesFilter.setFilterData(FILTER_TYPES.TAG, { excluded: [], selected: [tagId], bogus: true, });
}) const filterData = structuredClone(entitiesFilter.getFilterData(FILTER_TYPES.TAG));
if (!Array.isArray(filterData.selected)) {
filterData.selected = [];
filterData.excluded = [];
filterData.bogus = false;
}
if (tagId === 'back') {
filterData.selected.pop();
filterData.bogus = filterData.selected.length > 0;
} else {
filterData.selected.push(tagId);
filterData.bogus = true;
}
entitiesFilter.setFilterData(FILTER_TYPES.TAG, filterData);
});
$(document).on("input", ".edit_textarea", function () { $(document).on("input", ".edit_textarea", function () {
scroll_holder = $("#chat").scrollTop(); scroll_holder = $("#chat").scrollTop();

View File

@ -371,7 +371,7 @@ function RA_checkOnlineStatus() {
connection_made = false; connection_made = false;
} else { } else {
if (online_status !== undefined && online_status !== "no_connection") { if (online_status !== undefined && online_status !== "no_connection") {
$("#send_textarea").attr("placeholder", `Type a message, or /? for command list`); //on connect, placeholder tells user to type message $("#send_textarea").attr("placeholder", `Type a message, or /? for help`); //on connect, placeholder tells user to type message
$('#send_form').removeClass("no-connection"); $('#send_form').removeClass("no-connection");
$("#API-status-top").removeClass("fa-plug-circle-exclamation redOverlayGlow"); $("#API-status-top").removeClass("fa-plug-circle-exclamation redOverlayGlow");
$("#API-status-top").addClass("fa-plug"); $("#API-status-top").addClass("fa-plug");

View File

@ -69,6 +69,20 @@ export class FilterHelper {
return data.filter(entity => fuzzySearchResults.includes(entity.uid)); return data.filter(entity => fuzzySearchResults.includes(entity.uid));
} }
/**
* Checks if the given entity is tagged with the given tag ID.
* @param {object} entity Searchable entity
* @param {string} tagId Tag ID to check
* @returns {boolean} Whether the entity is tagged with the given tag ID
*/
isElementTagged(entity, tagId) {
const isCharacter = entity.type === 'character';
const lookupValue = isCharacter ? entity.item.avatar : String(entity.id);
const isTagged = Array.isArray(tag_map[lookupValue]) && tag_map[lookupValue].includes(tagId);
return isTagged;
}
/** /**
* Applies a tag filter to the data. * Applies a tag filter to the data.
* @param {any[]} data The data to filter. * @param {any[]} data The data to filter.
@ -82,19 +96,12 @@ export class FilterHelper {
return data; return data;
} }
function isElementTagged(entity, tagId) { const getIsTagged = (entity) => {
const isCharacter = entity.type === 'character'; const tagFlags = selected.map(tagId => this.isElementTagged(entity, tagId));
const lookupValue = isCharacter ? entity.item.avatar : String(entity.id);
const isTagged = Array.isArray(tag_map[lookupValue]) && tag_map[lookupValue].includes(tagId);
return isTagged;
}
function getIsTagged(entity) {
const tagFlags = selected.map(tagId => isElementTagged(entity, tagId));
const trueFlags = tagFlags.filter(x => x); const trueFlags = tagFlags.filter(x => x);
const isTagged = TAG_LOGIC_AND ? tagFlags.length === trueFlags.length : trueFlags.length > 0; const isTagged = TAG_LOGIC_AND ? tagFlags.length === trueFlags.length : trueFlags.length > 0;
const excludedTagFlags = excluded.map(tagId => isElementTagged(entity, tagId)); const excludedTagFlags = excluded.map(tagId => this.isElementTagged(entity, tagId));
const isExcluded = excludedTagFlags.includes(true); const isExcluded = excludedTagFlags.includes(true);
if (isExcluded) { if (isExcluded) {

View File

@ -39,7 +39,23 @@ async function uploadUserAvatar(url, name) {
} }
async function createDummyPersona() { async function createDummyPersona() {
await uploadUserAvatar(default_avatar); const personaName = await callPopup('<h3>Enter a name for this persona:</h3>', 'input', '');
if (!personaName) {
console.debug('User cancelled creating dummy persona');
return;
}
// Date + name (only ASCII) to make it unique
const avatarId = `${Date.now()}-${personaName.replace(/[^a-zA-Z0-9]/g, '')}.png`;
power_user.personas[avatarId] = personaName;
power_user.persona_descriptions[avatarId] = {
description: '',
position: persona_description_positions.IN_PROMPT,
};
await uploadUserAvatar(default_avatar, avatarId);
saveSettingsDebounced();
} }
export async function convertCharacterToPersona(characterId = null) { export async function convertCharacterToPersona(characterId = null) {

View File

@ -1651,7 +1651,17 @@ function sortEntitiesList(entities) {
return; return;
} }
entities.sort((a, b) => sortFunc(a.item, b.item)); entities.sort((a, b) => {
if (a.type === 'tag' && b.type !== 'tag') {
return -1;
}
if (a.type !== 'tag' && b.type === 'tag') {
return 1;
}
return sortFunc(a.item, b.item);
});
} }
async function saveTheme() { async function saveTheme() {

View File

@ -26,7 +26,7 @@ import {
setCharacterName, setCharacterName,
} from "../script.js"; } from "../script.js";
import { getMessageTimeStamp } from "./RossAscends-mods.js"; import { getMessageTimeStamp } from "./RossAscends-mods.js";
import { groups, resetSelectedGroup, selected_group } from "./group-chats.js"; import { groups, is_group_generating, resetSelectedGroup, selected_group } from "./group-chats.js";
import { getRegexedString, regex_placement } from "./extensions/regex/engine.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";
import { autoSelectPersona } from "./personas.js"; import { autoSelectPersona } from "./personas.js";
@ -270,7 +270,12 @@ async function unhideMessageCallback(_, arg) {
async function triggerGroupMessageCallback(_, arg) { async function triggerGroupMessageCallback(_, arg) {
if (!selected_group) { if (!selected_group) {
toastr.warning("Cannot run this command outside of a group chat."); toastr.warning("Cannot run trigger command outside of a group chat.");
return;
}
if (is_group_generating) {
toastr.warning("Cannot run trigger command while the group reply is generating.");
return; return;
} }

View File

@ -38,14 +38,13 @@ export const tag_filter_types = {
}; };
const ACTIONABLE_TAGS = { const ACTIONABLE_TAGS = {
FAV: { id: 1, name: 'Show only favorites', color: 'rgba(255, 255, 0, 0.5)', action: applyFavFilter, icon: 'fa-solid fa-star', class: 'filterByFavorites' }, FAV: { id: 1, name: 'Show only favorites', color: 'rgba(255, 255, 0, 0.5)', action: applyFavFilter, icon: 'fa-solid fa-star', class: 'filterByFavorites' },
GROUP: { id: 0, name: 'Show only groups', color: 'rgba(100, 100, 100, 0.5)', action: filterByGroups, icon: 'fa-solid fa-users', class: 'filterByGroups' }, GROUP: { id: 0, name: 'Show only groups', color: 'rgba(100, 100, 100, 0.5)', action: filterByGroups, icon: 'fa-solid fa-users', class: 'filterByGroups' },
VIEW: { id: 2, name: 'Manage tags', color: 'rgba(150, 100, 100, 0.5)', action: onViewTagsListClick, icon: 'fa-solid fa-gear', class: 'manageTags' },
HINT: { id: 3, name: 'Show Tag List', color: 'rgba(150, 100, 100, 0.5)', action: onTagListHintClick, icon: 'fa-solid fa-tags', class: 'showTagList' }, HINT: { id: 3, name: 'Show Tag List', color: 'rgba(150, 100, 100, 0.5)', action: onTagListHintClick, icon: 'fa-solid fa-tags', class: 'showTagList' },
} }
const InListActionable = { const InListActionable = {
VIEW: { id: 2, name: 'Manage tags', color: 'rgba(150, 100, 100, 0.5)', action: onViewTagsListClick, icon: 'fa-solid fa-gear' },
} }
const DEFAULT_TAGS = [ const DEFAULT_TAGS = [
@ -321,9 +320,9 @@ function appendTagToList(listElement, tag, { removable, selectable, action, isGe
tagElement.on('click', () => action.bind(tagElement)(filter)); tagElement.on('click', () => action.bind(tagElement)(filter));
tagElement.addClass('actionable'); tagElement.addClass('actionable');
} }
if (action && tag.id === 2) { /*if (action && tag.id === 2) {
tagElement.addClass('innerActionable hidden'); tagElement.addClass('innerActionable hidden');
} }*/
$(listElement).append(tagElement); $(listElement).append(tagElement);
} }

View File

@ -1,26 +1,26 @@
System-wide Replacement Macros (in order of evaluation): System-wide Replacement Macros (in order of evaluation):
<ul> <ul>
<li><tt>&lcub;&lcub;original&rcub;&rcub;</tt> - global prompts defined in API settings. Only valid in Advanced Definitions prompt overrides.</li> <li><tt>&lcub;&lcub;original&rcub;&rcub;</tt> global prompts defined in API settings. Only valid in Advanced Definitions prompt overrides.</li>
<li><tt>&lcub;&lcub;input&rcub;&rcub;</tt> - the user input</li> <li><tt>&lcub;&lcub;input&rcub;&rcub;</tt> the user input</li>
<li><tt>&lcub;&lcub;description&rcub;&rcub;</tt> - the Character's Description</li> <li><tt>&lcub;&lcub;description&rcub;&rcub;</tt> the Character's Description</li>
<li><tt>&lcub;&lcub;personality&rcub;&rcub;</tt> - the Character's Personality</li> <li><tt>&lcub;&lcub;personality&rcub;&rcub;</tt> the Character's Personality</li>
<li><tt>&lcub;&lcub;scenario&rcub;&rcub;</tt> - the Character's Scenario</li> <li><tt>&lcub;&lcub;scenario&rcub;&rcub;</tt> the Character's Scenario</li>
<li><tt>&lcub;&lcub;persona&rcub;&rcub;</tt> - your current Persona Description</li> <li><tt>&lcub;&lcub;persona&rcub;&rcub;</tt> your current Persona Description</li>
<li><tt>&lcub;&lcub;mesExamples&rcub;&rcub;</tt> - the Character's Dialogue Examples</li> <li><tt>&lcub;&lcub;mesExamples&rcub;&rcub;</tt> the Character's Dialogue Examples</li>
<li><tt>&lcub;&lcub;user&rcub;&rcub;</tt> - your current Persona username</li> <li><tt>&lcub;&lcub;user&rcub;&rcub;</tt> your current Persona username</li>
<li><tt>&lcub;&lcub;char&rcub;&rcub;</tt> - the Character's name</li> <li><tt>&lcub;&lcub;char&rcub;&rcub;</tt> the Character's name</li>
<li><tt>&lcub;&lcub;lastMessageId&rcub;&rcub;</tt> - index # of the latest chat message. Useful for slash command batching.</li> <li><tt>&lcub;&lcub;lastMessageId&rcub;&rcub;</tt> index # of the latest chat message. Useful for slash command batching.</li>
<li><tt>&lcub;&lcub;// (note)&rcub;&rcub;</tt> - you can leave a note here, and the macro will be replaced with blank content. Not visible for the AI.</li> <li><tt>&lcub;&lcub;// (note)&rcub;&rcub;</tt> you can leave a note here, and the macro will be replaced with blank content. Not visible for the AI.</li>
<li><tt>&lcub;&lcub;time&rcub;&rcub;</tt> - the current time</li> <li><tt>&lcub;&lcub;time&rcub;&rcub;</tt> the current time</li>
<li><tt>&lcub;&lcub;date&rcub;&rcub;</tt> - the current date</li> <li><tt>&lcub;&lcub;date&rcub;&rcub;</tt> the current date</li>
<li><tt>&lcub;&lcub;weekday&rcub;&rcub;</tt> - the current weekday</li> <li><tt>&lcub;&lcub;weekday&rcub;&rcub;</tt> the current weekday</li>
<li><tt>&lcub;&lcub;isotime&rcub;&rcub;</tt> - the current ISO date (YYYY-MM-DD)</li> <li><tt>&lcub;&lcub;isotime&rcub;&rcub;</tt> the current ISO date (YYYY-MM-DD)</li>
<li><tt>&lcub;&lcub;isodate&rcub;&rcub;</tt> - the current ISO time (24-hour clock)</li> <li><tt>&lcub;&lcub;isodate&rcub;&rcub;</tt> the current ISO time (24-hour clock)</li>
<li><tt>&lcub;&lcub;datetimeformat &hellip;&rcub;&rcub;</tt> - the current date/time in the specified format, e. g. for German date/time: <tt>&lcub;&lcub;datetimeformat DD.MM.YYYY HH:mm&rcub;&rcub;</tt></li> <li><tt>&lcub;&lcub;datetimeformat &hellip;&rcub;&rcub;</tt> the current date/time in the specified format, e. g. for German date/time: <tt>&lcub;&lcub;datetimeformat DD.MM.YYYY HH:mm&rcub;&rcub;</tt></li>
<li><tt>&lcub;&lcub;time_UTC±#&rcub;&rcub;</tt> - the current time in the specified UTC time zone offset, e.g. UTC-4 or UTC+2</li> <li><tt>&lcub;&lcub;time_UTC±#&rcub;&rcub;</tt> the current time in the specified UTC time zone offset, e.g. UTC-4 or UTC+2</li>
<li><tt>&lcub;&lcub;idle_duration&rcub;&rcub;</tt> - the time since the last user message was sent</li> <li><tt>&lcub;&lcub;idle_duration&rcub;&rcub;</tt> the time since the last user message was sent</li>
<li><tt>&lcub;&lcub;bias "text here"&rcub;&rcub;</tt> - sets a behavioral bias for the AI until the next user input. Quotes around the text are important.</li> <li><tt>&lcub;&lcub;bias "text here"&rcub;&rcub;</tt> sets a behavioral bias for the AI until the next user input. Quotes around the text are important.</li>
<li><tt>&lcub;&lcub;random:(args)&rcub;&rcub;</tt> - returns a random item from the list. (ex: &lcub;&lcub;random:1,2,3,4&rcub;&rcub; will return 1 of the 4 numbers at random. Works with text lists too.</li> <li><tt>&lcub;&lcub;random:(args)&rcub;&rcub;</tt> returns a random item from the list. (ex: &lcub;&lcub;random:1,2,3,4&rcub;&rcub; will return 1 of the 4 numbers at random. Works with text lists too.</li>
<li><tt>&lcub;&lcub;roll:(formula)&rcub;&rcub;</tt> - rolls a dice. (ex: &lcub;&lcub;roll:1d6&rcub;&rcub; will roll a 6-sided dice and return a number between 1 and 6)</li> <li><tt>&lcub;&lcub;roll:(formula)&rcub;&rcub;</tt> rolls a dice. (ex: &lcub;&lcub;roll:1d6&rcub;&rcub; will roll a 6- sided dice and return a number between 1 and 6)</li>
<li><tt>&lcub;&lcub;banned "text here"&rcub;&rcub;</tt> - dynamically add text in the quotes to banned words sequences, if Text Generation WebUI backend used. Do nothing for others backends. Can be used anywhere (Character description, WI, AN, etc.) Quotes around the text are important.</li> <li><tt>&lcub;&lcub;banned "text here"&rcub;&rcub;</tt> dynamically add text in the quotes to banned words sequences, if Text Generation WebUI backend used. Do nothing for others backends. Can be used anywhere (Character description, WI, AN, etc.) Quotes around the text are important.</li>
</ul> </ul>