mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-02-22 06:57:41 +01:00
Merge branch 'staging' into parser-followup-2
This commit is contained in:
commit
4ca0cb2aeb
@ -109,7 +109,6 @@ dialog {
|
||||
|
||||
.menu_button.popup-button-ok {
|
||||
background-color: var(--crimson70a);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menu_button.popup-button-ok:hover {
|
||||
@ -132,3 +131,17 @@ dialog {
|
||||
filter: brightness(1.3) saturate(1.3);
|
||||
}
|
||||
|
||||
.popup .popup-button-close {
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
right: -6px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
font-size: 20px;
|
||||
padding: 2px 3px 3px 2px;
|
||||
|
||||
filter: brightness(0.8);
|
||||
|
||||
/* Fix weird animation issue with font-scaling during popup open */
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
@ -3923,13 +3923,22 @@
|
||||
<h4 data-i18n="Character Handling">
|
||||
Character Handling
|
||||
</h4>
|
||||
<div title="If set in the advanced character definitions, this field will be displayed in the characters list." data-i18n="[title]If set in the advanced character definitions, this field will be displayed in the characters list.">
|
||||
<div class="flex-container alignitemscenter" title="If set in the advanced character definitions, this field will be displayed in the characters list." data-i18n="[title]If set in the advanced character definitions, this field will be displayed in the characters list.">
|
||||
<label for="aux_field"><small data-i18n="Char List Subheader">Char List Subheader</small></label>
|
||||
<select id="aux_field">
|
||||
<select id="aux_field" class="widthNatural flex1 margin0">
|
||||
<option data-i18n="Character Version" value="character_version">Character Version</option>
|
||||
<option data-i18n="Created by" value="creator">Created by</option>
|
||||
</select>
|
||||
</div>
|
||||
<div data-newbie-hidden class="flex-container alignitemscenter" title="Defines on importing cards which action should be chosen for importing its listed tags. 'Ask' will always display the dialog." data-i18n="[title]Defines on importing cards which action should be chosen for importing its listed tags. 'Ask' will always display the dialog.">
|
||||
<label for="tag_import_setting"><small data-i18n="Import Card Tags">Import Card Tags</small></label>
|
||||
<select id="tag_import_setting" class="widthNatural flex1 margin0">
|
||||
<option data-i18n="Ask" value="1">Ask</option>
|
||||
<option data-i18n="None" value="2">None</option>
|
||||
<option data-i18n="All" value="3">All</option>
|
||||
<option data-i18n="Existing" value="4">Existing</option>
|
||||
</select>
|
||||
</div>
|
||||
<label data-newbie-hidden class="checkbox_label" for="fuzzy_search_checkbox" title="Use fuzzy matching, and search characters in the list by all data fields, not just by a name substring." data-i18n="[title]Use fuzzy matching, and search characters in the list by all data fields, not just by a name substring">
|
||||
<input id="fuzzy_search_checkbox" type="checkbox" />
|
||||
<small data-i18n="Advanced Character Search">Advanced Character Search</small>
|
||||
@ -3950,10 +3959,6 @@
|
||||
<input id="show_card_avatar_urls" type="checkbox" />
|
||||
<small data-i18n="Show avatar filenames">Show avatar filenames</small>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="import_card_tags" title="Prompt to import embedded card tags on character import. Otherwise embedded tags are ignored." data-i18n="[title]Prompt to import embedded card tags on character import. Otherwise embedded tags are ignored">
|
||||
<input id="import_card_tags" type="checkbox" />
|
||||
<small data-i18n="Import Card Tags">Import Card Tags</small>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="spoiler_free_mode" title="Hide character definitions from the editor panel behind a spoiler button." data-i18n="[title]Hide character definitions from the editor panel behind a spoiler button">
|
||||
<input id="spoiler_free_mode" type="checkbox" />
|
||||
<small data-i18n="Spoiler Free Mode">Spoiler Free Mode</small>
|
||||
@ -4850,10 +4855,11 @@
|
||||
</div>
|
||||
<textarea class="popup-input text_pole result-control" rows="1" data-result="1"></textarea>
|
||||
<div class="popup-controls">
|
||||
<div class="popup-button-ok menu_button result-control" data-i18n="Delete" data-result="1" tabindex="0">Delete</div>
|
||||
<div class="popup-button-cancel menu_button result-control" data-i18n="Cancel" data-result="0" tabindex="0">Cancel</div>
|
||||
<div class="popup-button-ok menu_button result-control" data-result="1" data-i18n="Delete">Delete</div>
|
||||
<div class="popup-button-cancel menu_button result-control" data-result="0" data-i18n="Cancel">Cancel</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="popup-button-close right_menu_button fa-solid fa-circle-xmark" data-result="0" title="Close popup" data-i18n="[title]Close popup"></div>
|
||||
</dialog>
|
||||
</template>
|
||||
<div id="shadow_popup">
|
||||
@ -5465,7 +5471,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="character_template" class="template_element">
|
||||
<div class="character_select flex-container wide100p alignitemsflexstart" chid="" id="">
|
||||
<div class="character_select entity_block flex-container wide100p alignitemsflexstart" chid="" id="">
|
||||
<div class="avatar" title="">
|
||||
<img src="">
|
||||
</div>
|
||||
@ -5766,7 +5772,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="group_list_template" class="template_element">
|
||||
<div class="group_select flex-container wide100p alignitemsflexstart">
|
||||
<div class="group_select entity_block flex-container wide100p alignitemsflexstart">
|
||||
<div class="avatar">
|
||||
<img src="">
|
||||
</div>
|
||||
@ -5784,7 +5790,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="bogus_folder_template" class="template_element">
|
||||
<div class="bogus_folder_select flex-container wide100p alignitemsflexstart">
|
||||
<div class="bogus_folder_select entity_block flex-container wide100p alignitemsflexstart">
|
||||
<div class="avatar flex alignitemscenter textAlignCenter">
|
||||
<i class="bogus_folder_icon fa-solid fa-xl"></i>
|
||||
</div>
|
||||
|
@ -3,6 +3,7 @@
|
||||
"checkJs": true,
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"allowUmdGlobalAccess": true,
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
|
@ -175,11 +175,12 @@ import {
|
||||
createTagMapFromList,
|
||||
renameTagKey,
|
||||
importTags,
|
||||
tag_filter_types,
|
||||
tag_filter_type,
|
||||
compareTagsForSort,
|
||||
initTags,
|
||||
applyTagsOnCharacterSelect,
|
||||
applyTagsOnGroupSelect,
|
||||
tag_import_setting,
|
||||
} from './scripts/tags.js';
|
||||
import {
|
||||
SECRET_KEYS,
|
||||
@ -238,6 +239,8 @@ import { initCustomSelectedSamplers, validateDisabledSamplers } from './scripts/
|
||||
import { DragAndDropHandler } from './scripts/dragdrop.js';
|
||||
import { INTERACTABLE_CONTROL_CLASS, initKeyboard } from './scripts/keyboard.js';
|
||||
import { initDynamicStyles } from './scripts/dynamic-styles.js';
|
||||
import { SlashCommandEnumValue, enumTypes } from './scripts/slash-commands/SlashCommandEnumValue.js';
|
||||
import { enumIcons } from './scripts/slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
|
||||
//exporting functions and vars for mods
|
||||
export {
|
||||
@ -431,7 +434,9 @@ export const event_types = {
|
||||
CHARACTER_MESSAGE_RENDERED: 'character_message_rendered',
|
||||
FORCE_SET_BACKGROUND: 'force_set_background',
|
||||
CHAT_DELETED: 'chat_deleted',
|
||||
CHAT_CREATED: 'chat_created',
|
||||
GROUP_CHAT_DELETED: 'group_chat_deleted',
|
||||
GROUP_CHAT_CREATED: 'group_chat_created',
|
||||
GENERATE_BEFORE_COMBINE_PROMPTS: 'generate_before_combine_prompts',
|
||||
GENERATE_AFTER_COMBINE_PROMPTS: 'generate_after_combine_prompts',
|
||||
GROUP_MEMBER_DRAFTED: 'group_member_drafted',
|
||||
@ -1344,8 +1349,8 @@ export async function printCharacters(fullRefresh = false) {
|
||||
verifyCharactersSearchSortRule();
|
||||
|
||||
// We are actually always reprinting filters, as it "doesn't hurt", and this way they are always up to date
|
||||
printTagFilters(tag_filter_types.character);
|
||||
printTagFilters(tag_filter_types.group_member);
|
||||
printTagFilters(tag_filter_type.character);
|
||||
printTagFilters(tag_filter_type.group_member);
|
||||
|
||||
// We are also always reprinting the lists on character/group edit window, as these ones doesn't get updated otherwise
|
||||
applyTagsOnCharacterSelect();
|
||||
@ -1366,7 +1371,7 @@ export async function printCharacters(fullRefresh = false) {
|
||||
nextText: '>',
|
||||
formatNavigator: PAGINATION_TEMPLATE,
|
||||
showNavigator: true,
|
||||
callback: function (data) {
|
||||
callback: function (/** @type {Entity[]} */ data) {
|
||||
$(listId).empty();
|
||||
if (power_user.bogus_folders && isBogusFolderOpen()) {
|
||||
$(listId).append(getBackBlock());
|
||||
@ -1386,7 +1391,7 @@ export async function printCharacters(fullRefresh = false) {
|
||||
displayCount++;
|
||||
break;
|
||||
case 'tag':
|
||||
$(listId).append(getTagBlock(i.item, i.entities, i.hidden));
|
||||
$(listId).append(getTagBlock(i.item, i.entities, i.hidden, i.isUseless));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1440,8 +1445,9 @@ function verifyCharactersSearchSortRule() {
|
||||
* @property {Character|Group|import('./scripts/tags.js').Tag|*} item - The item
|
||||
* @property {string|number} id - The id
|
||||
* @property {'character'|'group'|'tag'} type - The type of this entity (character, group, tag)
|
||||
* @property {Entity[]} [entities] - An optional list of entities relevant for this item
|
||||
* @property {number} [hidden] - An optional number representing how many hidden entities this entity contains
|
||||
* @property {Entity[]?} [entities=null] - An optional list of entities relevant for this item
|
||||
* @property {number?} [hidden=null] - An optional number representing how many hidden entities this entity contains
|
||||
* @property {boolean?} [isUseless=null] - Specifies if the entity is useless (not relevant, but should still be displayed for consistency) and should be displayed greyed out
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -1536,6 +1542,15 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
// Final step, updating some properties after the last filter run
|
||||
const nonTagEntitiesCount = entities.filter(entity => entity.type !== 'tag').length;
|
||||
for (const entity of entities) {
|
||||
if (entity.type === 'tag') {
|
||||
if (entity.entities?.length == nonTagEntitiesCount) entity.isUseless = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort before returning if requested
|
||||
if (doSort) {
|
||||
sortEntitiesList(entities);
|
||||
}
|
||||
@ -2109,7 +2124,7 @@ export function addCopyToCodeBlocks(messageElement) {
|
||||
hljs.highlightElement(codeBlocks.get(i));
|
||||
if (navigator.clipboard !== undefined) {
|
||||
const copyButton = document.createElement('i');
|
||||
copyButton.classList.add('fa-solid', 'fa-copy', 'code-copy');
|
||||
copyButton.classList.add('fa-solid', 'fa-copy', 'code-copy', 'interactable');
|
||||
copyButton.title = 'Copy code';
|
||||
codeBlocks.get(i).appendChild(copyButton);
|
||||
copyButton.addEventListener('pointerup', function (event) {
|
||||
@ -2363,6 +2378,10 @@ export function substituteParamsExtended(content, additionalMacro = {}) {
|
||||
* @returns {string} The string with substituted parameters.
|
||||
*/
|
||||
export function substituteParams(content, _name1, _name2, _original, _group, _replaceCharacterCard = true, additionalMacro = {}) {
|
||||
if (!content) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const environment = {};
|
||||
|
||||
if (typeof _original === 'string') {
|
||||
@ -5884,11 +5903,13 @@ export async function getChat() {
|
||||
|
||||
async function getChatResult() {
|
||||
name2 = characters[this_chid].name;
|
||||
let freshChat = false;
|
||||
if (chat.length === 0) {
|
||||
const message = getFirstMessage();
|
||||
if (message.mes) {
|
||||
chat.push(message);
|
||||
await saveChatConditional();
|
||||
freshChat = true;
|
||||
}
|
||||
}
|
||||
await loadItemizedPrompts(getCurrentChatId());
|
||||
@ -5896,6 +5917,7 @@ async function getChatResult() {
|
||||
select_selected_character(this_chid);
|
||||
|
||||
await eventSource.emit(event_types.CHAT_CHANGED, (getCurrentChatId()));
|
||||
if (freshChat) await eventSource.emit(event_types.CHAT_CREATED);
|
||||
|
||||
if (chat.length === 1) {
|
||||
const chat_id = (chat.length - 1);
|
||||
@ -8459,7 +8481,7 @@ async function importCharacter(file, preserveFileName = false) {
|
||||
|
||||
await getCharacters();
|
||||
select_rm_info('char_import', data.file_name, oldSelectedChar);
|
||||
if (power_user.import_card_tags) {
|
||||
if (power_user.tag_import_setting !== tag_import_setting.NONE) {
|
||||
let currentContext = getContext();
|
||||
let avatarFileName = `${data.file_name}.png`;
|
||||
let importedCharacter = currentContext.characters.find(character => character.avatar === avatarFileName);
|
||||
@ -8801,6 +8823,9 @@ jQuery(async function () {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Collect all unique API names in an array
|
||||
const uniqueAPIs = [...new Set(Object.values(CONNECT_API_MAP).map(x => x.selected))];
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'dupe',
|
||||
callback: DupeChar,
|
||||
@ -8809,16 +8834,15 @@ jQuery(async function () {
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'api',
|
||||
callback: connectAPISlash,
|
||||
namedArgumentList: [],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'API to connect to',
|
||||
[ARGUMENT_TYPE.STRING],
|
||||
true,
|
||||
false,
|
||||
null,
|
||||
Object.keys(CONNECT_API_MAP),
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'API to connect to',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: false,
|
||||
enumList: Object.entries(CONNECT_API_MAP).map(([api, { selected }]) =>
|
||||
new SlashCommandEnumValue(api, selected, enumTypes.getBasedOnIndex(uniqueAPIs.findIndex(x => x === selected)),
|
||||
selected[0].toUpperCase() ?? enumIcons.default)),
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
@ -8907,11 +8931,12 @@ jQuery(async function () {
|
||||
name: 'instruct',
|
||||
callback: selectInstructCallback,
|
||||
returns: 'current preset',
|
||||
namedArgumentList: [],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'name', [ARGUMENT_TYPE.STRING], false,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'instruct preset name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: () => instruct_presets.map(preset => new SlashCommandEnumValue(preset.name, null, enumTypes.enum, enumIcons.preset)),
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
@ -8942,9 +8967,11 @@ jQuery(async function () {
|
||||
callback: selectContextCallback,
|
||||
returns: 'template name',
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'name', [ARGUMENT_TYPE.STRING], false,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'context preset name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: () => context_presets.map(preset => new SlashCommandEnumValue(preset.name, null, enumTypes.enum, enumIcons.preset)),
|
||||
}),
|
||||
],
|
||||
helpString: 'Selects context template by name. Gets the current template if no name is provided',
|
||||
}));
|
||||
@ -10594,7 +10621,7 @@ jQuery(async function () {
|
||||
}
|
||||
} break;
|
||||
case 'import_tags': {
|
||||
await importTags(characters[this_chid]);
|
||||
await importTags(characters[this_chid], { forceShow: true });
|
||||
} break;
|
||||
/*case 'delete_button':
|
||||
popup_type = "del_ch";
|
||||
|
@ -38,6 +38,7 @@ const chara_note_position = {
|
||||
function setNoteTextCommand(_, text) {
|
||||
$('#extension_floating_prompt').val(text).trigger('input');
|
||||
toastr.success('Author\'s Note text updated');
|
||||
return '';
|
||||
}
|
||||
|
||||
function setNoteDepthCommand(_, text) {
|
||||
@ -50,6 +51,7 @@ function setNoteDepthCommand(_, text) {
|
||||
|
||||
$('#extension_floating_depth').val(Math.abs(value)).trigger('input');
|
||||
toastr.success('Author\'s Note depth updated');
|
||||
return '';
|
||||
}
|
||||
|
||||
function setNoteIntervalCommand(_, text) {
|
||||
@ -62,6 +64,7 @@ function setNoteIntervalCommand(_, text) {
|
||||
|
||||
$('#extension_floating_interval').val(Math.abs(value)).trigger('input');
|
||||
toastr.success('Author\'s Note frequency updated');
|
||||
return '';
|
||||
}
|
||||
|
||||
function setNotePositionCommand(_, text) {
|
||||
@ -79,6 +82,7 @@ function setNotePositionCommand(_, text) {
|
||||
|
||||
$(`input[name="extension_floating_position"][value="${position}"]`).prop('checked', true).trigger('input');
|
||||
toastr.info('Author\'s Note position updated');
|
||||
return '';
|
||||
}
|
||||
|
||||
function updateSettings() {
|
||||
|
@ -761,8 +761,10 @@ export class AutoComplete {
|
||||
}
|
||||
// autocomplete shown or not, cursor anywhere
|
||||
switch (evt.key) {
|
||||
// The first is a non-breaking space, the second is a regular space.
|
||||
case ' ':
|
||||
case ' ': {
|
||||
if (evt.ctrlKey) {
|
||||
if (evt.ctrlKey || evt.altKey) {
|
||||
if (this.isActive && this.isReplaceable) {
|
||||
// ctrl-space to toggle details for selected item
|
||||
this.toggleDetails();
|
||||
@ -770,6 +772,8 @@ export class AutoComplete {
|
||||
// ctrl-space to force show autocomplete
|
||||
this.show(false, true);
|
||||
}
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
@ -147,6 +147,11 @@ export class AutoCompleteOption {
|
||||
}
|
||||
li.append(specs);
|
||||
}
|
||||
const stopgap = document.createElement('span'); {
|
||||
stopgap.classList.add('stopgap');
|
||||
stopgap.textContent = '';
|
||||
li.append(stopgap);
|
||||
}
|
||||
const help = document.createElement('span'); {
|
||||
help.classList.add('help');
|
||||
const content = document.createElement('span'); {
|
||||
|
@ -95,7 +95,7 @@ function onLockBackgroundClick(e) {
|
||||
|
||||
if (!chatName) {
|
||||
toastr.warning('Select a chat to lock the background for it');
|
||||
return;
|
||||
return '';
|
||||
}
|
||||
|
||||
const relativeBgImage = getUrlParameter(this);
|
||||
@ -103,6 +103,7 @@ function onLockBackgroundClick(e) {
|
||||
saveBackgroundMetadata(relativeBgImage);
|
||||
setCustomBackground();
|
||||
highlightLockedBackground();
|
||||
return '';
|
||||
}
|
||||
|
||||
function onUnlockBackgroundClick(e) {
|
||||
@ -110,6 +111,7 @@ function onUnlockBackgroundClick(e) {
|
||||
removeBackgroundMetadata();
|
||||
unsetCustomBackground();
|
||||
highlightLockedBackground();
|
||||
return '';
|
||||
}
|
||||
|
||||
function hasCustomBackground() {
|
||||
@ -319,7 +321,7 @@ async function autoBackgroundCommand() {
|
||||
const options = bgTitles.map(x => ({ element: x, text: x.innerText.trim() })).filter(x => x.text.length > 0);
|
||||
if (options.length == 0) {
|
||||
toastr.warning('No backgrounds to choose from. Please upload some images to the "backgrounds" folder.');
|
||||
return;
|
||||
return '';
|
||||
}
|
||||
|
||||
const list = options.map(option => `- ${option.text}`).join('\n');
|
||||
@ -330,11 +332,12 @@ async function autoBackgroundCommand() {
|
||||
|
||||
if (bestMatch.length == 0) {
|
||||
toastr.warning('No match found. Please try again.');
|
||||
return;
|
||||
return '';
|
||||
}
|
||||
|
||||
console.debug('Automatically choosing background:', bestMatch);
|
||||
bestMatch[0].item.element.click();
|
||||
return '';
|
||||
}
|
||||
|
||||
export async function getBackgrounds() {
|
||||
|
@ -35,7 +35,7 @@ import {
|
||||
extractTextFromOffice,
|
||||
} from './utils.js';
|
||||
import { extension_settings, renderExtensionTemplateAsync, saveMetadataDebounced } from './extensions.js';
|
||||
import { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from './popup.js';
|
||||
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
|
||||
import { ScraperManager } from './scrapers.js';
|
||||
import { DragAndDropHandler } from './dragdrop.js';
|
||||
|
||||
@ -566,7 +566,7 @@ export function isExternalMediaAllowed() {
|
||||
return !power_user.forbid_external_media;
|
||||
}
|
||||
|
||||
function enlargeMessageImage() {
|
||||
async function enlargeMessageImage() {
|
||||
const mesBlock = $(this).closest('.mes');
|
||||
const mesId = mesBlock.attr('mesid');
|
||||
const message = chat[mesId];
|
||||
@ -580,14 +580,28 @@ function enlargeMessageImage() {
|
||||
const img = document.createElement('img');
|
||||
img.classList.add('img_enlarged');
|
||||
img.src = imgSrc;
|
||||
const imgHolder = document.createElement('div');
|
||||
imgHolder.classList.add('img_enlarged_holder');
|
||||
imgHolder.append(img);
|
||||
const imgContainer = $('<div><pre><code></code></pre></div>');
|
||||
imgContainer.prepend(img);
|
||||
imgContainer.prepend(imgHolder);
|
||||
imgContainer.addClass('img_enlarged_container');
|
||||
imgContainer.find('code').addClass('txt').text(title);
|
||||
const titleEmpty = !title || title.trim().length === 0;
|
||||
imgContainer.find('pre').toggle(!titleEmpty);
|
||||
addCopyToCodeBlocks(imgContainer);
|
||||
callGenericPopup(imgContainer, POPUP_TYPE.TEXT, '', { wide: true, large: true });
|
||||
|
||||
const popup = new Popup(imgContainer, POPUP_TYPE.DISPLAY, '', { large: true, transparent: true });
|
||||
|
||||
popup.dlg.style.width = 'unset';
|
||||
popup.dlg.style.height = 'unset';
|
||||
|
||||
img.addEventListener('click', () => {
|
||||
const shouldZoom = !img.classList.contains('zoomed');
|
||||
img.classList.toggle('zoomed', shouldZoom);
|
||||
});
|
||||
|
||||
await popup.show();
|
||||
}
|
||||
|
||||
async function deleteMessageImage() {
|
||||
|
@ -2,6 +2,10 @@ import { deleteAttachment, getDataBankAttachments, getDataBankAttachmentsForSour
|
||||
import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js';
|
||||
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||
import { SlashCommandClosure } from '../../slash-commands/SlashCommandClosure.js';
|
||||
import { enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js';
|
||||
import { SlashCommandExecutor } from '../../slash-commands/SlashCommandExecutor.js';
|
||||
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
||||
|
||||
/**
|
||||
@ -196,9 +200,31 @@ jQuery(async () => {
|
||||
const buttons = await renderExtensionTemplateAsync('attachments', 'buttons', {});
|
||||
$('#extensionsMenu').prepend(buttons);
|
||||
|
||||
/** A collection of local enum providers for this context of data bank */
|
||||
const localEnumProviders = {
|
||||
/**
|
||||
* All attachments in the data bank based on the source argument. If not provided, defaults to 'chat'.
|
||||
* @param {'name' | 'url'} returnField - Whether the enum should return the 'name' field or the 'url'
|
||||
* @param {'chat' | 'character' | 'global' | ''} fallbackSource - The source to use if the source argument is not provided. Empty string to use all sources.
|
||||
* */
|
||||
attachments: (returnField = 'name', fallbackSource = 'chat') => (/** @type {SlashCommandExecutor} */ executor) => {
|
||||
const source = executor.namedArgumentList.find(it => it.name == 'source')?.value ?? fallbackSource;
|
||||
if (source instanceof SlashCommandClosure) throw new Error('Argument \'source\' does not support closures');
|
||||
const attachments = getAttachments(source);
|
||||
|
||||
return attachments.map(attachment => new SlashCommandEnumValue(
|
||||
returnField === 'name' ? attachment.name : attachment.url,
|
||||
`${enumIcons.getStateIcon(!extension_settings.disabled_attachments.includes(attachment.url))} [${source}] ${returnField === 'url' ? attachment.name : attachment.url}`,
|
||||
enumTypes.enum, enumIcons.file));
|
||||
},
|
||||
};
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'db',
|
||||
callback: () => document.getElementById('manageAttachments')?.click(),
|
||||
callback: () => {
|
||||
document.getElementById('manageAttachments')?.click();
|
||||
return '';
|
||||
},
|
||||
aliases: ['databank', 'data-bank'],
|
||||
helpString: 'Open the data bank',
|
||||
}));
|
||||
@ -224,7 +250,13 @@ jQuery(async () => {
|
||||
new SlashCommandNamedArgument('source', 'The source of the attachment.', ARGUMENT_TYPE.STRING, false, false, '', TYPES),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument('The name or URL of the attachment.', ARGUMENT_TYPE.STRING, true, false),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'The name or URL of the attachment.',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
acceptsMultiple: false,
|
||||
enumProvider: localEnumProviders.attachments('name', ''),
|
||||
}),
|
||||
],
|
||||
returns: ARGUMENT_TYPE.STRING,
|
||||
}));
|
||||
@ -251,8 +283,18 @@ jQuery(async () => {
|
||||
helpString: 'Update an attachment in the Data Bank, preserving its name. Returns a new URL of the attachment.',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument('source', 'The source for the attachment.', ARGUMENT_TYPE.STRING, false, false, 'chat', TYPES),
|
||||
new SlashCommandNamedArgument('name', 'The name of the attachment.', ARGUMENT_TYPE.STRING, false, false),
|
||||
new SlashCommandNamedArgument('url', 'The URL of the attachment to update.', ARGUMENT_TYPE.STRING, false, false),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'name',
|
||||
description: 'The name of the attachment.',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: localEnumProviders.attachments('name'),
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'url',
|
||||
description: 'The URL of the attachment.',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: localEnumProviders.attachments('url'),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument('The content of the file attachment.', ARGUMENT_TYPE.STRING, true, false),
|
||||
@ -269,7 +311,12 @@ jQuery(async () => {
|
||||
new SlashCommandNamedArgument('source', 'The source of the attachment.', ARGUMENT_TYPE.STRING, false, false, '', TYPES),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument('The name or URL of the attachment.', ARGUMENT_TYPE.STRING, true, false),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'The name or URL of the attachment.',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.attachments('name', ''),
|
||||
}),
|
||||
],
|
||||
}));
|
||||
|
||||
@ -282,7 +329,12 @@ jQuery(async () => {
|
||||
new SlashCommandNamedArgument('source', 'The source of the attachment.', ARGUMENT_TYPE.STRING, false, false, '', TYPES),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument('The name or URL of the attachment.', ARGUMENT_TYPE.STRING, true, false),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'The name or URL of the attachment.',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.attachments('name', ''),
|
||||
}),
|
||||
],
|
||||
}));
|
||||
|
||||
@ -295,7 +347,12 @@ jQuery(async () => {
|
||||
new SlashCommandNamedArgument('source', 'The source of the attachment.', ARGUMENT_TYPE.STRING, false, false, 'chat', TYPES),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument('The name or URL of the attachment.', ARGUMENT_TYPE.STRING, true, false),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'The name or URL of the attachment.',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.attachments(),
|
||||
}),
|
||||
],
|
||||
}));
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js';
|
||||
import { ensureImageFormatSupported, getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js';
|
||||
import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js';
|
||||
import { callPopup, getRequestHeaders, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js';
|
||||
import { getMessageTimeStamp } from '../../RossAscends-mods.js';
|
||||
@ -8,6 +8,8 @@ import { textgen_types, textgenerationwebui_settings } from '../../textgen-setti
|
||||
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
||||
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||
import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';
|
||||
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
export { MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = 'caption';
|
||||
@ -272,7 +274,7 @@ async function getCaptionForFile(file, prompt, quiet) {
|
||||
try {
|
||||
setSpinnerIcon();
|
||||
const context = getContext();
|
||||
const fileData = await getBase64Async(file);
|
||||
const fileData = await getBase64Async(await ensureImageFormatSupported(file));
|
||||
const base64Format = fileData.split(',')[0].split(';')[0].split('/')[1];
|
||||
const base64Data = fileData.split(',')[1];
|
||||
const { caption } = await doCaptionRequest(base64Data, fileData, prompt);
|
||||
@ -377,6 +379,12 @@ jQuery(async function () {
|
||||
}
|
||||
function switchMultimodalBlocks() {
|
||||
const isMultimodal = extension_settings.caption.source === 'multimodal';
|
||||
$('#caption_ollama_pull').on('click', (e) => {
|
||||
const presetModel = extension_settings.caption.multimodal_model !== 'ollama_current' ? extension_settings.caption.multimodal_model : '';
|
||||
e.preventDefault();
|
||||
$('#ollama_download_model').trigger('click');
|
||||
$('#dialogue_popup_input').val(presetModel);
|
||||
});
|
||||
$('#caption_multimodal_block').toggle(isMultimodal);
|
||||
$('#caption_prompt_block').toggle(isMultimodal);
|
||||
$('#caption_multimodal_api').val(extension_settings.caption.multimodal_api);
|
||||
@ -445,11 +453,14 @@ jQuery(async function () {
|
||||
returns: 'caption',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'quiet', 'suppress sending a captioned message', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', ['true', 'false'],
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'id', 'get image from a message with this ID', [ARGUMENT_TYPE.NUMBER], false, false,
|
||||
'quiet', 'suppress sending a captioned message', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'id',
|
||||
description: 'get image from a message with this ID',
|
||||
typeList: [ARGUMENT_TYPE.NUMBER],
|
||||
enumProvider: commonEnumProviders.messages(),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
|
@ -58,14 +58,20 @@
|
||||
<option data-type="openrouter" value="google/gemini-flash-1.5">google/gemini-flash-1.5</option>
|
||||
<option data-type="openrouter" value="liuhaotian/llava-yi-34b">liuhaotian/llava-yi-34b</option>
|
||||
<option data-type="ollama" value="ollama_current" data-i18n="currently_selected">[Currently selected]</option>
|
||||
<option data-type="ollama" value="bakllava:latest">bakllava:latest</option>
|
||||
<option data-type="ollama" value="llava:latest">llava:latest</option>
|
||||
<option data-type="ollama" value="bakllava">bakllava</option>
|
||||
<option data-type="ollama" value="llava">llava</option>
|
||||
<option data-type="ollama" value="llava-llama3">llava-llama3</option>
|
||||
<option data-type="ollama" value="llava-phi3">llava-phi3</option>
|
||||
<option data-type="ollama" value="moondream">moondream</option>
|
||||
<option data-type="llamacpp" value="llamacpp_current" data-i18n="currently_loaded">[Currently loaded]</option>
|
||||
<option data-type="ooba" value="ooba_current" data-i18n="currently_loaded">[Currently loaded]</option>
|
||||
<option data-type="koboldcpp" value="koboldcpp_current" data-i18n="currently_loaded">[Currently loaded]</option>
|
||||
<option data-type="custom" value="custom_current" data-i18n="currently_selected">[Currently selected]</option>
|
||||
</select>
|
||||
</div>
|
||||
<div data-type="ollama">
|
||||
The model must be downloaded first! Do it with the <code>ollama pull</code> command or <a href="#" id="caption_ollama_pull">click here</a>.
|
||||
</div>
|
||||
<label data-type="openai,anthropic,google" class="checkbox_label flexBasis100p" for="caption_allow_reverse_proxy" title="Allow using reverse proxy if defined and valid.">
|
||||
<input id="caption_allow_reverse_proxy" type="checkbox" class="checkbox">
|
||||
<span data-i18n="Allow reverse proxy">Allow reverse proxy</span>
|
||||
|
@ -10,7 +10,8 @@ import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
||||
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||
import { isFunctionCallingSupported } from '../../openai.js';
|
||||
import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';
|
||||
import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js';
|
||||
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
export { MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = 'expressions';
|
||||
@ -2044,6 +2045,14 @@ function migrateSettings() {
|
||||
});
|
||||
eventSource.on(event_types.MOVABLE_PANELS_RESET, updateVisualNovelModeDebounced);
|
||||
eventSource.on(event_types.GROUP_UPDATED, updateVisualNovelModeDebounced);
|
||||
|
||||
const localEnumProviders = {
|
||||
expressions: () => getCachedExpressions().map(expression => {
|
||||
const isCustom = extension_settings.expressions.custom?.includes(expression);
|
||||
return new SlashCommandEnumValue(expression, null, isCustom ? enumTypes.name : enumTypes.enum, isCustom ? 'C' : 'D');
|
||||
}),
|
||||
};
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'sprite',
|
||||
aliases: ['emote'],
|
||||
@ -2053,7 +2062,7 @@ function migrateSettings() {
|
||||
description: 'spriteId',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: () => getCachedExpressions().map((x) => new SlashCommandEnumValue(x)),
|
||||
enumProvider: localEnumProviders.expressions,
|
||||
}),
|
||||
],
|
||||
helpString: 'Force sets the sprite for the current character.',
|
||||
@ -2075,9 +2084,12 @@ function migrateSettings() {
|
||||
callback: (_, value) => lastExpression[String(value).trim()] ?? '',
|
||||
returns: 'sprite',
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'charName', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'character name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: commonEnumProviders.characters('character'),
|
||||
}),
|
||||
],
|
||||
helpString: 'Returns the last set sprite / expression for the named character.',
|
||||
}));
|
||||
|
@ -12,6 +12,7 @@ import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
||||
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||
import { DragAndDropHandler } from '../../dragdrop.js';
|
||||
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
|
||||
const extensionName = 'gallery';
|
||||
const extensionFolderPath = `scripts/extensions/${extensionName}/`;
|
||||
@ -401,7 +402,10 @@ function viewWithDragbox(items) {
|
||||
// Registers a simple command for opening the char gallery.
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'show-gallery',
|
||||
aliases: ['sg'],
|
||||
callback: showGalleryCommand,
|
||||
callback: () => {
|
||||
showCharGallery();
|
||||
return '';
|
||||
},
|
||||
helpString: 'Shows the gallery.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'list-gallery',
|
||||
@ -409,21 +413,22 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'list-gallery
|
||||
callback: listGalleryCommand,
|
||||
returns: 'list of images',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'char', 'character name', [ARGUMENT_TYPE.STRING], false,
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'group', 'group name', [ARGUMENT_TYPE.STRING], false,
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'char',
|
||||
description: 'character name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: commonEnumProviders.characters('character'),
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'group',
|
||||
description: 'group name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: commonEnumProviders.characters('group'),
|
||||
}),
|
||||
],
|
||||
helpString: 'List images in the gallery of the current char / group or a specified char / group.',
|
||||
}));
|
||||
|
||||
|
||||
function showGalleryCommand(args) {
|
||||
showCharGallery();
|
||||
}
|
||||
|
||||
async function listGalleryCommand(args) {
|
||||
try {
|
||||
let url = args.char ?? (args.group ? groups.find(it=>it.name == args.group)?.id : null) ?? (selected_group || this_chid);
|
||||
|
@ -24,6 +24,7 @@ import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
||||
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||
import { resolveVariable } from '../../variables.js';
|
||||
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
export { MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = '1_memory';
|
||||
@ -919,7 +920,14 @@ jQuery(async function () {
|
||||
callback: summarizeCallback,
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument('source', 'API to use for summarization', [ARGUMENT_TYPE.STRING], false, false, '', ['main', 'extras']),
|
||||
new SlashCommandNamedArgument('prompt', 'prompt to use for summarization', [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME], false, false, ''),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'prompt',
|
||||
description: 'prompt to use for summarization',
|
||||
typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME],
|
||||
defaultValue: '',
|
||||
enumProvider: commonEnumProviders.variables('all'),
|
||||
forceEnum: false,
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument('text to summarize', [ARGUMENT_TYPE.STRING], false, false, ''),
|
||||
|
@ -1,9 +1,13 @@
|
||||
import { SlashCommand } from '../../../slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../../slash-commands/SlashCommandArgument.js';
|
||||
import { enumIcons } from '../../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
import { SlashCommandEnumValue, enumTypes } from '../../../slash-commands/SlashCommandEnumValue.js';
|
||||
import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js';
|
||||
import { isTrueBoolean } from '../../../utils.js';
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { QuickReplyApi } from '../api/QuickReplyApi.js';
|
||||
import { QuickReply } from './QuickReply.js';
|
||||
import { QuickReplySet } from './QuickReplySet.js';
|
||||
|
||||
export class SlashCommandHandler {
|
||||
/**@type {QuickReplyApi}*/ api;
|
||||
@ -19,6 +23,50 @@ export class SlashCommandHandler {
|
||||
|
||||
|
||||
init() {
|
||||
function getExecutionIcons(/**@type {QuickReply} */ qr) {
|
||||
let icons = '';
|
||||
if (qr.preventAutoExecute) icons += '🚫';
|
||||
if (qr.isHidden) icons += '👁️';
|
||||
if (qr.executeOnStartup) icons += '🚀';
|
||||
if (qr.executeOnUser) icons += enumIcons.user;
|
||||
if (qr.executeOnAi) icons += enumIcons.assistant;
|
||||
if (qr.executeOnChatChange) icons += '💬';
|
||||
if (qr.executeOnGroupMemberDraft) icons += enumIcons.group;
|
||||
return icons;
|
||||
}
|
||||
|
||||
const localEnumProviders = {
|
||||
/** All quick reply sets, optionally filtering out sets that wer already used in the "set" named argument */
|
||||
qrSets: (executor) => QuickReplySet.list.filter(qrSet => qrSet.name != String(executor.namedArgumentList.find(x => x.name == 'set')?.value))
|
||||
.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, enumTypes.enum, 'S')),
|
||||
|
||||
/** All QRs inside a set, utilizing the "set" named argument */
|
||||
qrEntries: (executor) => QuickReplySet.get(String(executor.namedArgumentList.find(x => x.name == 'set')?.value))?.qrList.map(qr => {
|
||||
const icons = getExecutionIcons(qr);
|
||||
const message = `${qr.automationId ? `[${qr.automationId}]` : ''}${icons ? `[auto: ${icons}]` : ''} ${qr.title || qr.message}`.trim();
|
||||
return new SlashCommandEnumValue(qr.label, message, enumTypes.enum, enumIcons.qr);
|
||||
}) ?? [],
|
||||
|
||||
/** All QRs as a set.name string, to be able to execute, for example via the /run command */
|
||||
qrExecutables: () => {
|
||||
const globalSetList = this.api.settings.config.setList;
|
||||
const chatSetList = this.api.settings.chatConfig?.setList;
|
||||
|
||||
const globalQrs = globalSetList.map(link => link.set.qrList.map(qr => ({ set: link.set, qr }))).flat();
|
||||
const chatQrs = chatSetList?.map(link => link.set.qrList.map(qr => ({ set: link.set, qr }))).flat() ?? [];
|
||||
const otherQrs = QuickReplySet.list.filter(set => !globalSetList.some(link => link.set.name === set.name && !chatSetList?.some(link => link.set.name === set.name)))
|
||||
.map(set => set.qrList.map(qr => ({ set, qr }))).flat();
|
||||
|
||||
return [
|
||||
...globalQrs.map(x => new SlashCommandEnumValue(`${x.set.name}.${x.qr.label}`, `[global] ${x.qr.title || x.qr.message}`, enumTypes.name, enumIcons.qr)),
|
||||
...chatQrs.map(x => new SlashCommandEnumValue(`${x.set.name}.${x.qr.label}`, `[chat] ${x.qr.title || x.qr.message}`, enumTypes.enum, enumIcons.qr)),
|
||||
...otherQrs.map(x => new SlashCommandEnumValue(`${x.set.name}.${x.qr.label}`, `${x.qr.title || x.qr.message}`, enumTypes.qr, enumIcons.qr)),
|
||||
];
|
||||
},
|
||||
}
|
||||
|
||||
window['qrEnumProviderExecutables'] = localEnumProviders.qrExecutables;
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr',
|
||||
callback: (_, value) => this.executeQuickReplyByIndex(Number(value)),
|
||||
unnamedArgumentList: [
|
||||
@ -29,110 +77,166 @@ export class SlashCommandHandler {
|
||||
helpString: 'Activates the specified Quick Reply',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qrset',
|
||||
callback: () => toastr.warning('The command /qrset has been deprecated. Use /qr-set, /qr-set-on, and /qr-set-off instead.'),
|
||||
callback: () => {
|
||||
toastr.warning('The command /qrset has been deprecated. Use /qr-set, /qr-set-on, and /qr-set-off instead.');
|
||||
return '';
|
||||
},
|
||||
helpString: '<strong>DEPRECATED</strong> – The command /qrset has been deprecated. Use /qr-set, /qr-set-on, and /qr-set-off instead.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set',
|
||||
callback: (args, value) => this.toggleGlobalSet(value, args),
|
||||
callback: (args, value) => {
|
||||
this.toggleGlobalSet(value, args);
|
||||
return '';
|
||||
},
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'visible', 'set visibility', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true',
|
||||
),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'QR set name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
],
|
||||
helpString: 'Toggle global QR set',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-on',
|
||||
callback: (args, value) => this.addGlobalSet(value, args),
|
||||
callback: (args, value) => {
|
||||
this.addGlobalSet(value, args);
|
||||
return '';
|
||||
},
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'visible', 'set visibility', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true',
|
||||
),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'QR set name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
],
|
||||
helpString: 'Activate global QR set',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-off',
|
||||
callback: (_, value) => this.removeGlobalSet(value),
|
||||
callback: (_, value) => {
|
||||
this.removeGlobalSet(value);
|
||||
return '';
|
||||
},
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'QR set name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
],
|
||||
helpString: 'Deactivate global QR set',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-chat-set',
|
||||
callback: (args, value) => this.toggleChatSet(value, args),
|
||||
callback: (args, value) => {
|
||||
this.toggleChatSet(value, args);
|
||||
return '';
|
||||
},
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'visible', 'set visibility', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true',
|
||||
),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'QR set name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
],
|
||||
helpString: 'Toggle chat QR set',
|
||||
}));
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-chat-set-on',
|
||||
callback: (args, value) => this.addChatSet(value, args),
|
||||
callback: (args, value) => {
|
||||
this.addChatSet(value, args);
|
||||
return '';
|
||||
},
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'visible', 'whether the QR set should be visible', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true', ['true', 'false'],
|
||||
'visible', 'whether the QR set should be visible', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true',
|
||||
),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'QR set name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
],
|
||||
helpString: 'Activate chat QR set',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-chat-set-off',
|
||||
callback: (_, value) => this.removeChatSet(value),
|
||||
callback: (_, value) => {
|
||||
this.removeChatSet(value);
|
||||
return '';
|
||||
},
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'QR set name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
],
|
||||
helpString: 'Deactivate chat QR set',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-list',
|
||||
callback: (_, value) => this.listSets(value ?? 'all'),
|
||||
callback: (_, value) => JSON.stringify(this.listSets(value ?? 'all')),
|
||||
returns: 'list of QR sets',
|
||||
namedArgumentList: [],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'set type', [ARGUMENT_TYPE.STRING], false, false, null, ['all', 'global', 'chat'],
|
||||
'set type', [ARGUMENT_TYPE.STRING], false, false, 'all', ['all', 'global', 'chat'],
|
||||
),
|
||||
],
|
||||
helpString: 'Gets a list of the names of all quick reply sets.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-list',
|
||||
callback: (_, value) => this.listQuickReplies(value),
|
||||
callback: (_, value) => {
|
||||
return JSON.stringify(this.listQuickReplies(value));
|
||||
},
|
||||
returns: 'list of QRs',
|
||||
namedArgumentList: [],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'set name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
],
|
||||
helpString: 'Gets a list of the names of all quick replies in this quick reply set.',
|
||||
}));
|
||||
|
||||
const qrArgs = [
|
||||
new SlashCommandNamedArgument('label', 'text on the button, e.g., label=MyButton', [ARGUMENT_TYPE.STRING]),
|
||||
new SlashCommandNamedArgument('set', 'name of the QR set, e.g., set=PresetName1', [ARGUMENT_TYPE.STRING]),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'set',
|
||||
description: 'name of the QR set, e.g., set=PresetName1',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'label',
|
||||
description: 'text on the button, e.g., label=MyButton',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrLabels,
|
||||
}),
|
||||
new SlashCommandNamedArgument('hidden', 'whether the button should be hidden, e.g., hidden=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
|
||||
new SlashCommandNamedArgument('startup', 'auto execute on app startup, e.g., startup=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
|
||||
new SlashCommandNamedArgument('user', 'auto execute on user message, e.g., user=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
|
||||
@ -144,8 +248,12 @@ export class SlashCommandHandler {
|
||||
const qrUpdateArgs = [
|
||||
new SlashCommandNamedArgument('newlabel', 'new text for the button', [ARGUMENT_TYPE.STRING], false),
|
||||
];
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-create',
|
||||
callback: (args, message) => this.createQuickReply(args, message),
|
||||
callback: (args, message) => {
|
||||
this.createQuickReply(args, message);
|
||||
return '';
|
||||
},
|
||||
namedArgumentList: qrArgs,
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
@ -165,9 +273,15 @@ export class SlashCommandHandler {
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-update',
|
||||
callback: (args, message) => this.updateQuickReply(args, message),
|
||||
callback: (args, message) => {
|
||||
this.updateQuickReply(args, message);
|
||||
return '';
|
||||
},
|
||||
returns: 'updated quick reply',
|
||||
namedArgumentList: [...qrUpdateArgs, ...qrArgs],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument('command', [ARGUMENT_TYPE.STRING]),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
Updates Quick Reply.
|
||||
@ -183,34 +297,57 @@ export class SlashCommandHandler {
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-delete',
|
||||
callback: (args, name) => this.deleteQuickReply(args, name),
|
||||
callback: (args, name) => {
|
||||
this.deleteQuickReply(args, name);
|
||||
return '';
|
||||
},
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'set', 'Quick Reply set', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'label', 'Quick Reply label', [ARGUMENT_TYPE.STRING], false,
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'set',
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'label',
|
||||
description: 'Quick Reply label',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: localEnumProviders.qrEntries,
|
||||
}),
|
||||
],
|
||||
helpString: 'Deletes a Quick Reply from the specified set. If no label is provided, the entire set is deleted.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-contextadd',
|
||||
callback: (args, name) => this.createContextItem(args, name),
|
||||
callback: (args, name) => {
|
||||
this.createContextItem(args, name);
|
||||
return '';
|
||||
},
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'set', 'string', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'label', 'string', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'set',
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'label',
|
||||
description: 'Quick Reply label',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: localEnumProviders.qrEntries,
|
||||
}),
|
||||
new SlashCommandNamedArgument(
|
||||
'chain', 'boolean', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',
|
||||
),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'preset name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
@ -227,19 +364,32 @@ export class SlashCommandHandler {
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-contextdel',
|
||||
callback: (args, name) => this.deleteContextItem(args, name),
|
||||
callback: (args, name) => {
|
||||
this.deleteContextItem(args, name);
|
||||
return '';
|
||||
},
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'set', 'string', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'label', 'string', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'set',
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'label',
|
||||
description: 'Quick Reply label',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: localEnumProviders.qrEntries,
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'preset name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
@ -256,16 +406,25 @@ export class SlashCommandHandler {
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-contextclear',
|
||||
callback: (args, label) => this.clearContextMenu(args, label),
|
||||
callback: (args, label) => {
|
||||
this.clearContextMenu(args, label);
|
||||
return '';
|
||||
},
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'set', 'context menu preset name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'set',
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'label', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'Quick Reply label',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: localEnumProviders.qrEntries,
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
@ -288,13 +447,20 @@ export class SlashCommandHandler {
|
||||
new SlashCommandNamedArgument('inject', 'inject user input automatically (if disabled use {{input}})', [ARGUMENT_TYPE.BOOLEAN], false),
|
||||
];
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-create',
|
||||
callback: (args, name) => this.createSet(name, args),
|
||||
callback: (args, name) => {
|
||||
this.createSet(name, args);
|
||||
return '';
|
||||
},
|
||||
aliases: ['qr-presetadd'],
|
||||
namedArgumentList: presetArgs,
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
forceEnum: false,
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
@ -312,11 +478,19 @@ export class SlashCommandHandler {
|
||||
}));
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-update',
|
||||
callback: (args, name) => this.updateSet(name, args),
|
||||
callback: (args, name) => {
|
||||
this.updateSet(name, args);
|
||||
return '';
|
||||
},
|
||||
aliases: ['qr-presetupdate'],
|
||||
namedArgumentList: presetArgs,
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument('name', [ARGUMENT_TYPE.STRING], true),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
@ -329,10 +503,18 @@ export class SlashCommandHandler {
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-delete',
|
||||
callback: (args, name) => this.deleteSet(name),
|
||||
callback: (_, name) => {
|
||||
this.deleteSet(name);
|
||||
return '';
|
||||
},
|
||||
aliases: ['qr-presetdelete'],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument('name', [ARGUMENT_TYPE.STRING], true),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
@ -468,7 +650,7 @@ export class SlashCommandHandler {
|
||||
}
|
||||
deleteQuickReply(args, label) {
|
||||
try {
|
||||
this.api.deleteQuickReply(args.set, label);
|
||||
this.api.deleteQuickReply(args.set, args.label ?? label);
|
||||
} catch (ex) {
|
||||
toastr.error(ex.message);
|
||||
}
|
||||
|
@ -148,7 +148,7 @@ export class SettingsUi {
|
||||
this.onQrSetChange();
|
||||
}
|
||||
onQrSetChange() {
|
||||
this.currentQrSet = QuickReplySet.get(this.currentSet.value);
|
||||
this.currentQrSet = QuickReplySet.get(this.currentSet.value) ?? new QuickReplySet();
|
||||
this.disableSend.checked = this.currentQrSet.disableSend;
|
||||
this.placeBeforeInput.checked = this.currentQrSet.placeBeforeInput;
|
||||
this.injectInput.checked = this.currentQrSet.injectInput;
|
||||
|
@ -92,7 +92,7 @@ function getRegexedString(rawString, placement, { characterOverride, isMarkdown,
|
||||
|
||||
/**
|
||||
* Runs the provided regex script on the given string
|
||||
* @param {object} regexScript The regex script to run
|
||||
* @param {import('./index.js').RegexScript} regexScript The regex script to run
|
||||
* @param {string} rawString The string to run the regex script on
|
||||
* @param {RegexScriptParams} params The parameters to use for the regex script
|
||||
* @returns {string} The new string
|
||||
|
@ -3,11 +3,32 @@ import { extension_settings, renderExtensionTemplateAsync, writeExtensionField }
|
||||
import { selected_group } from '../../group-chats.js';
|
||||
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||
import { enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js';
|
||||
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
||||
import { download, getFileText, getSortableDelay, uuidv4 } from '../../utils.js';
|
||||
import { resolveVariable } from '../../variables.js';
|
||||
import { regex_placement, runRegexScript } from './engine.js';
|
||||
|
||||
/**
|
||||
* @typedef {object} RegexScript
|
||||
* @property {string} scriptName - The name of the script
|
||||
* @property {boolean} disabled - Whether the script is disabled
|
||||
* @property {string} replaceString - The replace string
|
||||
* @property {string[]} trimStrings - The trim strings
|
||||
* @property {string?} findRegex - The find regex
|
||||
* @property {string?} substituteRegex - The substitute regex
|
||||
*/
|
||||
|
||||
/**
|
||||
* Retrieves the list of regex scripts by combining the scripts from the extension settings and the character data
|
||||
*
|
||||
* @return {RegexScript[]} An array of regex scripts, where each script is an object containing the necessary information.
|
||||
*/
|
||||
export function getRegexScripts() {
|
||||
return [...(extension_settings.regex ?? []), ...(characters[this_chid]?.data?.extensions?.regex_scripts ?? [])];
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a regex script to the extension settings or character data.
|
||||
* @param {import('../../char-data.js').RegexScriptData} regexScript
|
||||
@ -339,7 +360,7 @@ function runRegexCallback(args, value) {
|
||||
}
|
||||
|
||||
const scriptName = String(resolveVariable(args.name));
|
||||
const scripts = [...(extension_settings.regex ?? []), ...(characters[this_chid]?.data?.extensions?.regex_scripts ?? [])];
|
||||
const scripts = getRegexScripts();
|
||||
|
||||
for (const script of scripts) {
|
||||
if (String(script.scriptName).toLowerCase() === String(scriptName).toLowerCase()) {
|
||||
@ -551,14 +572,26 @@ jQuery(async () => {
|
||||
await loadRegexScripts();
|
||||
$('#saved_regex_scripts').sortable('enable');
|
||||
|
||||
const localEnumProviders = {
|
||||
regexScripts: () => getRegexScripts().map(script => {
|
||||
const isGlobal = extension_settings.regex?.some(x => x.scriptName === script.scriptName);
|
||||
return new SlashCommandEnumValue(script.scriptName, `${enumIcons.getStateIcon(!script.disabled)} [${isGlobal ? 'global' : 'scoped'}] ${script.findRegex}`,
|
||||
isGlobal ? enumTypes.enum : enumTypes.name, isGlobal ? 'G' : 'S');
|
||||
}),
|
||||
};
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'regex',
|
||||
callback: runRegexCallback,
|
||||
returns: 'replaced text',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'name', 'script name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'name',
|
||||
description: 'script name',
|
||||
typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.regexScripts,
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
|
@ -31,6 +31,8 @@ import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||
import { resolveVariable } from '../../variables.js';
|
||||
import { debounce_timeout } from '../../constants.js';
|
||||
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';
|
||||
export { MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = 'sd';
|
||||
@ -3349,11 +3351,14 @@ jQuery(async () => {
|
||||
aliases: ['sd', 'img', 'image'],
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'quiet', 'whether to post the generated image to chat', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', ['false', 'true'],
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'negative', 'negative prompt prefix', [ARGUMENT_TYPE.STRING], false, false, '',
|
||||
'quiet', 'whether to post the generated image to chat', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'negative',
|
||||
description: 'negative prompt prefix',
|
||||
typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME],
|
||||
enumProvider: commonEnumProviders.variables('all'),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
@ -3362,7 +3367,7 @@ jQuery(async () => {
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
Requests to generate an image and posts it to chat (unless quiet=true argument is specified). Supported arguments: <code>${Object.values(triggerWords).flat().join(', ')}</code>.
|
||||
Requests to generate an image and posts it to chat (unless <code>quiet=true</code> argument is specified).</code>.
|
||||
</div>
|
||||
<div>
|
||||
Anything else would trigger a "free mode" to make generate whatever you prompted. Example: <code>/imagine apple tree</code> would generate a picture of an apple tree. Returns a link to the generated image.
|
||||
@ -3375,9 +3380,12 @@ jQuery(async () => {
|
||||
callback: changeComfyWorkflow,
|
||||
aliases: ['icw'],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'workflowName', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'workflow name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: () => Array.from(document.querySelectorAll('#sd_comfy_workflow > [value]')).map(x => x.getAttribute('value')).map(workflow => new SlashCommandEnumValue(workflow)),
|
||||
}),
|
||||
],
|
||||
helpString: '(workflowName) - change the workflow to be used for image generation with ComfyUI, e.g. <pre><code>/imagine-comfy-workflow MyWorkflow</code></pre>',
|
||||
}));
|
||||
|
@ -123,6 +123,7 @@ async function doCount() {
|
||||
//toastr success with the token count of the chat
|
||||
const count = await getTokenCountAsync(allMessages);
|
||||
toastr.success(`Token count: ${count}`);
|
||||
return count;
|
||||
}
|
||||
|
||||
jQuery(() => {
|
||||
@ -134,7 +135,7 @@ jQuery(() => {
|
||||
$('#extensionsMenu').prepend(buttonHtml);
|
||||
$('#token_counter').on('click', doTokenCounter);
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'count',
|
||||
callback: doCount,
|
||||
callback: async () => String(await doCount()),
|
||||
returns: 'number of tokens',
|
||||
helpString: 'Counts the number of tokens in the current chat.',
|
||||
}));
|
||||
|
@ -19,6 +19,8 @@ import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
||||
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||
import { debounce_timeout } from '../../constants.js';
|
||||
import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js';
|
||||
import { enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
export { talkingAnimation };
|
||||
|
||||
const UPDATE_INTERVAL = 1000;
|
||||
@ -1204,12 +1206,19 @@ $(document).ready(function () {
|
||||
eventSource.makeLast(event_types.USER_MESSAGE_RENDERED, onMessageEvent);
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'speak',
|
||||
callback: onNarrateText,
|
||||
callback: async (args, value) => {
|
||||
await onNarrateText(args, value);
|
||||
return '';
|
||||
},
|
||||
aliases: ['narrate', 'tts'],
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'voice', 'character voice name', [ARGUMENT_TYPE.STRING], false,
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'voice',
|
||||
description: 'character voice name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: false,
|
||||
enumProvider: () => Object.keys(voiceMap).map(voiceName => new SlashCommandEnumValue(voiceName, null, enumTypes.enum, enumIcons.voice)),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
|
@ -20,7 +20,7 @@ import {
|
||||
renderExtensionTemplateAsync,
|
||||
doExtrasFetch, getApiUrl,
|
||||
} from '../../extensions.js';
|
||||
import { collapseNewlines } from '../../power-user.js';
|
||||
import { collapseNewlines, registerDebugFunction } from '../../power-user.js';
|
||||
import { SECRET_KEYS, secret_state, writeSecret } from '../../secrets.js';
|
||||
import { getDataBankAttachments, getDataBankAttachmentsForSource, getFileAttachment } from '../../chats.js';
|
||||
import { debounce, getStringHash as calculateHash, waitUntilCondition, onlyUnique, splitRecursive, trimToStartSentence, trimToEndSentence } from '../../utils.js';
|
||||
@ -989,6 +989,28 @@ async function purgeVectorIndex(collectionId) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Purges all vector indexes.
|
||||
*/
|
||||
async function purgeAllVectorIndexes() {
|
||||
try {
|
||||
const response = await fetch('/api/vector/purge-all', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to purge all vector indexes');
|
||||
}
|
||||
|
||||
console.log('Vectors: Purged all vector indexes');
|
||||
toastr.success('All vector indexes purged', 'Purge successful');
|
||||
} catch (error) {
|
||||
console.error('Vectors: Failed to purge all', error);
|
||||
toastr.error('Failed to purge all vector indexes', 'Purge failed');
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSettings() {
|
||||
$('#vectors_files_settings').toggle(!!settings.enabled_files);
|
||||
$('#vectors_chats_settings').toggle(!!settings.enabled_chats);
|
||||
@ -1502,6 +1524,13 @@ jQuery(async () => {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#vectors_ollama_pull').on('click', (e) => {
|
||||
const presetModel = extension_settings.vectors.ollama_model || '';
|
||||
e.preventDefault();
|
||||
$('#ollama_download_model').trigger('click');
|
||||
$('#dialogue_popup_input').val(presetModel);
|
||||
});
|
||||
|
||||
const validSecret = !!secret_state[SECRET_KEYS.NOMICAI];
|
||||
const placeholder = validSecret ? '✔️ Key saved' : '❌ Missing key';
|
||||
$('#api_key_nomicai').attr('placeholder', placeholder);
|
||||
@ -1571,4 +1600,11 @@ jQuery(async () => {
|
||||
],
|
||||
returns: ARGUMENT_TYPE.LIST,
|
||||
}));
|
||||
|
||||
registerDebugFunction('purge-everything', 'Purge all vector indices', 'Obliterate all stored vectors for all sources. No mercy.', async () => {
|
||||
if (!confirm('Are you sure?')) {
|
||||
return;
|
||||
}
|
||||
await purgeAllVectorIndexes();
|
||||
});
|
||||
});
|
||||
|
@ -32,8 +32,11 @@
|
||||
<input id="vectors_ollama_keep" type="checkbox" />
|
||||
<span data-i18n="Keep model in memory">Keep model in memory</span>
|
||||
</label>
|
||||
<i data-i18n="Hint: Download models and set the URL in the API connection settings.">
|
||||
Hint: Download models and set the URL in the API connection settings.
|
||||
<div>
|
||||
The model must be downloaded first! Do it with the <code>ollama pull</code> command or <a href="#" id="vectors_ollama_pull">click here</a>.
|
||||
</div>
|
||||
<i data-i18n="Hint: Set the URL in the API connection settings.">
|
||||
Hint: Set the URL in the API connection settings.
|
||||
</i>
|
||||
</div>
|
||||
<div class="flex-container flexFlowColumn" id="llamacpp_vectorsModel">
|
||||
|
@ -183,6 +183,7 @@ export async function getGroupChat(groupId, reload = false) {
|
||||
const group = groups.find((x) => x.id === groupId);
|
||||
const chat_id = group.chat_id;
|
||||
const data = await loadGroupChat(chat_id);
|
||||
let freshChat = false;
|
||||
|
||||
await loadItemizedPrompts(getCurrentChatId());
|
||||
|
||||
@ -216,6 +217,7 @@ export async function getGroupChat(groupId, reload = false) {
|
||||
}
|
||||
}
|
||||
await saveGroupChat(groupId, false);
|
||||
freshChat = true;
|
||||
}
|
||||
|
||||
if (group) {
|
||||
@ -228,11 +230,23 @@ export async function getGroupChat(groupId, reload = false) {
|
||||
}
|
||||
|
||||
await eventSource.emit(event_types.CHAT_CHANGED, getCurrentChatId());
|
||||
if (freshChat) await eventSource.emit(event_types.GROUP_CHAT_CREATED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the members of a group
|
||||
*
|
||||
* @param {string} [groupId=selected_group] - The ID of the group to retrieve members from. Defaults to the currently selected group.
|
||||
* @returns {import('../script.js').Character[]} An array of character objects representing the members of the group. If the group is not found, an empty array is returned.
|
||||
*/
|
||||
export function getGroupMembers(groupId = selected_group) {
|
||||
const group = groups.find((x) => x.id === groupId);
|
||||
return group?.members.map(member => characters.find(x => x.avatar === member)) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the character ID for a group member.
|
||||
* @param {string} arg 1-based member index or character name
|
||||
* @param {string} arg 0-based member index or character name
|
||||
* @returns {number} 0-based character ID
|
||||
*/
|
||||
export function findGroupMemberId(arg) {
|
||||
@ -250,8 +264,7 @@ export function findGroupMemberId(arg) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Index is 1-based
|
||||
const index = parseInt(arg) - 1;
|
||||
const index = parseInt(arg);
|
||||
const searchByName = isNaN(index);
|
||||
|
||||
if (searchByName) {
|
||||
|
@ -71,6 +71,7 @@ import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
|
||||
import { renderTemplateAsync } from './templates.js';
|
||||
import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
|
||||
|
||||
export {
|
||||
openai_messages_count,
|
||||
@ -199,16 +200,18 @@ const custom_prompt_post_processing_types = {
|
||||
CLAUDE: 'claude',
|
||||
};
|
||||
|
||||
const prefixMap = selected_group ? {
|
||||
assistant: '',
|
||||
user: '',
|
||||
system: 'OOC: ',
|
||||
function getPrefixMap() {
|
||||
return selected_group ? {
|
||||
assistant: '',
|
||||
user: '',
|
||||
system: 'OOC: ',
|
||||
}
|
||||
: {
|
||||
assistant: '{{char}}:',
|
||||
user: '{{user}}:',
|
||||
system: '',
|
||||
};
|
||||
}
|
||||
: {
|
||||
assistant: '{{char}}:',
|
||||
user: '{{user}}:',
|
||||
system: '',
|
||||
};
|
||||
|
||||
const default_settings = {
|
||||
preset_settings_openai: 'Default',
|
||||
@ -1709,7 +1712,7 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
|
||||
if (isAI21) {
|
||||
const joinedMsgs = messages.reduce((acc, obj) => {
|
||||
const prefix = prefixMap[obj.role];
|
||||
const prefix = getPrefixMap()[obj.role];
|
||||
return acc + (prefix ? (selected_group ? '\n' : prefix + ' ') : '') + obj.content + '\n';
|
||||
}, '');
|
||||
messages = substituteParams(joinedMsgs) + (isImpersonate ? `${name1}:` : `${name2}:`);
|
||||
@ -4602,9 +4605,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
returns: 'current proxy',
|
||||
namedArgumentList: [],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: () => proxies.map(preset => new SlashCommandEnumValue(preset.name, preset.url)),
|
||||
}),
|
||||
],
|
||||
helpString: 'Sets a proxy preset by name.',
|
||||
}));
|
||||
|
@ -3,31 +3,39 @@ import { removeFromArray, runAfterAnimation, uuidv4 } from './utils.js';
|
||||
/** @readonly */
|
||||
/** @enum {Number} */
|
||||
export const POPUP_TYPE = {
|
||||
'TEXT': 1,
|
||||
'CONFIRM': 2,
|
||||
'INPUT': 3,
|
||||
/** Main popup type. Containing any content displayed, with buttons below. Can also contain additional input controls. */
|
||||
TEXT: 1,
|
||||
/** Popup mainly made to confirm something, answering with a simple Yes/No or similar. Focus on the button controls. */
|
||||
CONFIRM: 2,
|
||||
/** Popup who's main focus is the input text field, which is displayed here. Can contain additional content above. Return value for this is the input string. */
|
||||
INPUT: 3,
|
||||
/** Popup without any button controls. Used to simply display content, with a small X in the corner. */
|
||||
DISPLAY: 4,
|
||||
};
|
||||
|
||||
/** @readonly */
|
||||
/** @enum {number?} */
|
||||
export const POPUP_RESULT = {
|
||||
'AFFIRMATIVE': 1,
|
||||
'NEGATIVE': 0,
|
||||
'CANCELLED': null,
|
||||
AFFIRMATIVE: 1,
|
||||
NEGATIVE: 0,
|
||||
CANCELLED: null,
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {object} PopupOptions
|
||||
* @property {string|boolean?} [okButton] - Custom text for the OK button, or `true` to use the default (If set, the button will always be displayed, no matter the type of popup)
|
||||
* @property {string|boolean?} [cancelButton] - Custom text for the Cancel button, or `true` to use the default (If set, the button will always be displayed, no matter the type of popup)
|
||||
* @property {number?} [rows] - The number of rows for the input field
|
||||
* @property {boolean?} [wide] - Whether to display the popup in wide mode (wide screen, 1/1 aspect ratio)
|
||||
* @property {boolean?} [wider] - Whether to display the popup in wider mode (just wider, no height scaling)
|
||||
* @property {boolean?} [large] - Whether to display the popup in large mode (90% of screen)
|
||||
* @property {boolean?} [allowHorizontalScrolling] - Whether to allow horizontal scrolling in the popup
|
||||
* @property {boolean?} [allowVerticalScrolling] - Whether to allow vertical scrolling in the popup
|
||||
* @property {POPUP_RESULT|number?} [defaultResult] - The default result of this popup when Enter is pressed. Can be changed from `POPUP_RESULT.AFFIRMATIVE`.
|
||||
* @property {CustomPopupButton[]|string[]?} [customButtons] - Custom buttons to add to the popup. If only strings are provided, the buttons will be added with default options, and their result will be in order from `2` onward.
|
||||
* @property {string|boolean?} [okButton=null] - Custom text for the OK button, or `true` to use the default (If set, the button will always be displayed, no matter the type of popup)
|
||||
* @property {string|boolean?} [cancelButton=null] - Custom text for the Cancel button, or `true` to use the default (If set, the button will always be displayed, no matter the type of popup)
|
||||
* @property {number?} [rows=1] - The number of rows for the input field
|
||||
* @property {boolean?} [wide=false] - Whether to display the popup in wide mode (wide screen, 1/1 aspect ratio)
|
||||
* @property {boolean?} [wider=false] - Whether to display the popup in wider mode (just wider, no height scaling)
|
||||
* @property {boolean?} [large=false] - Whether to display the popup in large mode (90% of screen)
|
||||
* @property {boolean?} [transparent=false] - Whether to display the popup in transparent mode (no background, border, shadow or anything, only its content)
|
||||
* @property {boolean?} [allowHorizontalScrolling=false] - Whether to allow horizontal scrolling in the popup
|
||||
* @property {boolean?} [allowVerticalScrolling=false] - Whether to allow vertical scrolling in the popup
|
||||
* @property {POPUP_RESULT|number?} [defaultResult=POPUP_RESULT.AFFIRMATIVE] - The default result of this popup when Enter is pressed. Can be changed from `POPUP_RESULT.AFFIRMATIVE`.
|
||||
* @property {CustomPopupButton[]|string[]?} [customButtons=null] - Custom buttons to add to the popup. If only strings are provided, the buttons will be added with default options, and their result will be in order from `2` onward.
|
||||
* @property {(popup: Popup) => boolean?} [onClosing=null] - Handler called before the popup closes, return `false` to cancel the close
|
||||
* @property {(popup: Popup) => void?} [onClose=null] - Handler called after the popup closes, but before the DOM is cleaned up
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -73,11 +81,15 @@ export class Popup {
|
||||
/** @type {HTMLElement} */ content;
|
||||
/** @type {HTMLTextAreaElement} */ input;
|
||||
/** @type {HTMLElement} */ controls;
|
||||
/** @type {HTMLElement} */ ok;
|
||||
/** @type {HTMLElement} */ cancel;
|
||||
/** @type {HTMLElement} */ okButton;
|
||||
/** @type {HTMLElement} */ cancelButton;
|
||||
/** @type {HTMLElement} */ closeButton;
|
||||
/** @type {POPUP_RESULT|number?} */ defaultResult;
|
||||
/** @type {CustomPopupButton[]|string[]?} */ customButtons;
|
||||
|
||||
/** @type {(popup: Popup) => boolean?} */ onClosing;
|
||||
/** @type {(popup: Popup) => void?} */ onClose;
|
||||
|
||||
/** @type {POPUP_RESULT|number} */ result;
|
||||
/** @type {any} */ value;
|
||||
|
||||
@ -94,13 +106,17 @@ export class Popup {
|
||||
* @param {string} [inputValue=''] - The initial value of the input field
|
||||
* @param {PopupOptions} [options={}] - Additional options for the popup
|
||||
*/
|
||||
constructor(content, type, inputValue = '', { okButton = null, cancelButton = null, rows = 1, wide = false, wider = false, large = false, allowHorizontalScrolling = false, allowVerticalScrolling = false, defaultResult = POPUP_RESULT.AFFIRMATIVE, customButtons = null } = {}) {
|
||||
constructor(content, type, inputValue = '', { okButton = null, cancelButton = null, rows = 1, wide = false, wider = false, large = false, transparent = false, allowHorizontalScrolling = false, allowVerticalScrolling = false, defaultResult = POPUP_RESULT.AFFIRMATIVE, customButtons = null, onClosing = null, onClose = null } = {}) {
|
||||
Popup.util.popups.push(this);
|
||||
|
||||
// Make this popup uniquely identifiable
|
||||
this.id = uuidv4();
|
||||
this.type = type;
|
||||
|
||||
// Utilize event handlers being passed in
|
||||
this.onClosing = onClosing;
|
||||
this.onClose = onClose;
|
||||
|
||||
/**@type {HTMLTemplateElement}*/
|
||||
const template = document.querySelector('#popup_template');
|
||||
// @ts-ignore
|
||||
@ -109,19 +125,21 @@ export class Popup {
|
||||
this.content = this.dlg.querySelector('.popup-content');
|
||||
this.input = this.dlg.querySelector('.popup-input');
|
||||
this.controls = this.dlg.querySelector('.popup-controls');
|
||||
this.ok = this.dlg.querySelector('.popup-button-ok');
|
||||
this.cancel = this.dlg.querySelector('.popup-button-cancel');
|
||||
this.okButton = this.dlg.querySelector('.popup-button-ok');
|
||||
this.cancelButton = this.dlg.querySelector('.popup-button-cancel');
|
||||
this.closeButton = this.dlg.querySelector('.popup-button-close');
|
||||
|
||||
this.dlg.setAttribute('data-id', this.id);
|
||||
if (wide) this.dlg.classList.add('wide_dialogue_popup');
|
||||
if (wider) this.dlg.classList.add('wider_dialogue_popup');
|
||||
if (large) this.dlg.classList.add('large_dialogue_popup');
|
||||
if (transparent) this.dlg.classList.add('transparent_dialogue_popup');
|
||||
if (allowHorizontalScrolling) this.dlg.classList.add('horizontal_scrolling_dialogue_popup');
|
||||
if (allowVerticalScrolling) this.dlg.classList.add('vertical_scrolling_dialogue_popup');
|
||||
|
||||
// If custom button captions are provided, we set them beforehand
|
||||
this.ok.textContent = typeof okButton === 'string' ? okButton : 'OK';
|
||||
this.cancel.textContent = typeof cancelButton === 'string' ? cancelButton : template.getAttribute('popup-button-cancel');
|
||||
this.okButton.textContent = typeof okButton === 'string' ? okButton : 'OK';
|
||||
this.cancelButton.textContent = typeof cancelButton === 'string' ? cancelButton : template.getAttribute('popup-button-cancel');
|
||||
|
||||
this.defaultResult = defaultResult;
|
||||
this.customButtons = customButtons;
|
||||
@ -132,17 +150,14 @@ export class Popup {
|
||||
const buttonElement = document.createElement('div');
|
||||
buttonElement.classList.add('menu_button', 'popup-button-custom', 'result-control');
|
||||
buttonElement.classList.add(...(button.classes ?? []));
|
||||
buttonElement.setAttribute('data-result', String(button.result ?? undefined));
|
||||
buttonElement.dataset.result = String(button.result ?? undefined);
|
||||
buttonElement.textContent = button.text;
|
||||
buttonElement.tabIndex = 0;
|
||||
|
||||
if (button.action) buttonElement.addEventListener('click', button.action);
|
||||
if (button.result) buttonElement.addEventListener('click', () => this.complete(button.result));
|
||||
|
||||
if (button.appendAtEnd) {
|
||||
this.controls.appendChild(buttonElement);
|
||||
} else {
|
||||
this.controls.insertBefore(buttonElement, this.ok);
|
||||
this.controls.insertBefore(buttonElement, this.okButton);
|
||||
}
|
||||
});
|
||||
|
||||
@ -150,23 +165,30 @@ export class Popup {
|
||||
const defaultButton = this.controls.querySelector(`[data-result="${this.defaultResult}"]`);
|
||||
if (defaultButton) defaultButton.classList.add('menu_button_default');
|
||||
|
||||
// Styling differences depending on the popup type
|
||||
// General styling for all types first, that might be overriden for specific types below
|
||||
this.input.style.display = 'none';
|
||||
this.closeButton.style.display = 'none';
|
||||
|
||||
switch (type) {
|
||||
case POPUP_TYPE.TEXT: {
|
||||
this.input.style.display = 'none';
|
||||
if (!cancelButton) this.cancel.style.display = 'none';
|
||||
if (!cancelButton) this.cancelButton.style.display = 'none';
|
||||
break;
|
||||
}
|
||||
case POPUP_TYPE.CONFIRM: {
|
||||
this.input.style.display = 'none';
|
||||
if (!okButton) this.ok.textContent = template.getAttribute('popup-button-yes');
|
||||
if (!cancelButton) this.cancel.textContent = template.getAttribute('popup-button-no');
|
||||
if (!okButton) this.okButton.textContent = template.getAttribute('popup-button-yes');
|
||||
if (!cancelButton) this.cancelButton.textContent = template.getAttribute('popup-button-no');
|
||||
break;
|
||||
}
|
||||
case POPUP_TYPE.INPUT: {
|
||||
this.input.style.display = 'block';
|
||||
if (!okButton) this.ok.textContent = template.getAttribute('popup-button-save');
|
||||
if (!okButton) this.okButton.textContent = template.getAttribute('popup-button-save');
|
||||
break;
|
||||
}
|
||||
case POPUP_TYPE.DISPLAY: {
|
||||
this.controls.style.display = 'none';
|
||||
this.closeButton.style.display = 'block';
|
||||
}
|
||||
default: {
|
||||
console.warn('Unknown popup type.', type);
|
||||
break;
|
||||
@ -193,8 +215,14 @@ export class Popup {
|
||||
// Set focus event that remembers the focused element
|
||||
this.dlg.addEventListener('focusin', (evt) => { if (evt.target instanceof HTMLElement && evt.target != this.dlg) this.lastFocus = evt.target; });
|
||||
|
||||
this.ok.addEventListener('click', () => this.complete(POPUP_RESULT.AFFIRMATIVE));
|
||||
this.cancel.addEventListener('click', () => this.complete(POPUP_RESULT.NEGATIVE));
|
||||
// Bind event listeners for all result controls to their defined event type
|
||||
this.dlg.querySelectorAll(`[data-result]`).forEach(resultControl => {
|
||||
if (!(resultControl instanceof HTMLElement)) return;
|
||||
const result = Number(resultControl.dataset.result);
|
||||
if (isNaN(result)) throw new Error('Invalid result control. Result must be a number. ' + resultControl.dataset.result);
|
||||
const type = resultControl.dataset.resultEvent || 'click';
|
||||
resultControl.addEventListener(type, () => this.complete(result));
|
||||
});
|
||||
|
||||
// Bind dialog listeners manually, so we can be sure context is preserved
|
||||
const cancelListener = (evt) => {
|
||||
@ -287,6 +315,9 @@ export class Popup {
|
||||
|
||||
if (applyAutoFocus) {
|
||||
control.setAttribute('autofocus', '');
|
||||
// Manually enable tabindex too, as this might only be applied by the interactable functionality in the background, but too late for HTML autofocus
|
||||
// interactable only gets applied when inserted into the DOM
|
||||
control.tabIndex = 0;
|
||||
} else {
|
||||
control.focus();
|
||||
}
|
||||
@ -317,6 +348,12 @@ export class Popup {
|
||||
|
||||
this.value = value;
|
||||
this.result = result;
|
||||
|
||||
if (this.onClosing) {
|
||||
const shouldClose = this.onClosing(this);
|
||||
if (!shouldClose) return;
|
||||
}
|
||||
|
||||
Popup.util.lastResult = { value, result };
|
||||
this.hide();
|
||||
}
|
||||
@ -337,6 +374,11 @@ export class Popup {
|
||||
// Call the close on the dialog
|
||||
this.dlg.close();
|
||||
|
||||
// Run a possible custom handler right before DOM removal
|
||||
if (this.onClose) {
|
||||
this.onClose(this);
|
||||
}
|
||||
|
||||
// Remove it from the dom
|
||||
this.dlg.remove();
|
||||
|
||||
|
@ -35,7 +35,7 @@ import {
|
||||
selectInstructPreset,
|
||||
} from './instruct-mode.js';
|
||||
|
||||
import { getTagsList, tag_map, tags } from './tags.js';
|
||||
import { getTagsList, tag_import_setting, tag_map, tags } from './tags.js';
|
||||
import { tokenizers } from './tokenizers.js';
|
||||
import { BIAS_CACHE } from './logit-bias.js';
|
||||
import { renderTemplateAsync } from './templates.js';
|
||||
@ -46,7 +46,8 @@ import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandPa
|
||||
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
|
||||
import { AUTOCOMPLETE_WIDTH } from './autocomplete/AutoComplete.js';
|
||||
import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
|
||||
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
|
||||
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
|
||||
export {
|
||||
loadPowerUserSettings,
|
||||
@ -198,6 +199,7 @@ let power_user = {
|
||||
trim_spaces: true,
|
||||
relaxed_api_urls: false,
|
||||
world_import_dialog: true,
|
||||
tag_import_setting: tag_import_setting.ASK,
|
||||
disable_group_trimming: false,
|
||||
single_line: false,
|
||||
|
||||
@ -1562,6 +1564,12 @@ function loadPowerUserSettings(settings, data) {
|
||||
power_user.tokenizer = tokenizers.GPT2;
|
||||
}
|
||||
|
||||
// Clean up old/legacy settings
|
||||
if (power_user.import_card_tags !== undefined) {
|
||||
power_user.tag_import_setting = power_user.import_card_tags ? tag_import_setting.ASK : tag_import_setting.NONE;
|
||||
delete power_user.import_card_tags;
|
||||
}
|
||||
|
||||
$('#single_line').prop('checked', power_user.single_line);
|
||||
$('#relaxed_api_urls').prop('checked', power_user.relaxed_api_urls);
|
||||
$('#world_import_dialog').prop('checked', power_user.world_import_dialog);
|
||||
@ -1590,7 +1598,6 @@ function loadPowerUserSettings(settings, data) {
|
||||
$('#zoomed_avatar_magnification').prop('checked', power_user.zoomed_avatar_magnification);
|
||||
$(`#tokenizer option[value="${power_user.tokenizer}"]`).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);
|
||||
$('#confirm_message_delete').prop('checked', power_user.confirm_message_delete !== undefined ? !!power_user.confirm_message_delete : true);
|
||||
$('#spoiler_free_mode').prop('checked', power_user.spoiler_free_mode);
|
||||
$('#collapse-newlines-checkbox').prop('checked', power_user.collapse_newlines);
|
||||
@ -1632,6 +1639,7 @@ function loadPowerUserSettings(settings, data) {
|
||||
$('#chat_width_slider').val(power_user.chat_width);
|
||||
$('#token_padding').val(power_user.token_padding);
|
||||
$('#aux_field').val(power_user.aux_field);
|
||||
$('#tag_import_setting').val(power_user.tag_import_setting);
|
||||
|
||||
$('#stscript_autocomplete_autoHide').prop('checked', power_user.stscript.autocomplete.autoHide ?? false).trigger('input');
|
||||
$('#stscript_matching').val(power_user.stscript.matching ?? 'fuzzy');
|
||||
@ -2910,7 +2918,7 @@ function setAvgBG() {
|
||||
return `rgba(${rNew.toFixed(0)}, ${gNew.toFixed(0)}, ${bNew.toFixed(0)}, 1)`;
|
||||
}
|
||||
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
async function setThemeCallback(_, text) {
|
||||
@ -3516,11 +3524,6 @@ $(document).ready(() => {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#import_card_tags').on('input', function () {
|
||||
power_user.import_card_tags = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#confirm_message_delete').on('input', function () {
|
||||
power_user.confirm_message_delete = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
@ -3759,6 +3762,12 @@ $(document).ready(() => {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#tag_import_setting').on('change', function () {
|
||||
const value = $(this).find(':selected').val();
|
||||
power_user.tag_import_setting = Number(value);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#stscript_autocomplete_autoHide').on('input', function () {
|
||||
power_user.stscript.autocomplete.autoHide = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
@ -3914,9 +3923,11 @@ $(document).ready(() => {
|
||||
name: 'random',
|
||||
callback: doRandomChat,
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'optional tag name', [ARGUMENT_TYPE.STRING], false,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'optional tag name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: () => tags.filter(tag => Object.values(tag_map).some(x => x.includes(tag.id))).map(tag => new SlashCommandEnumValue(tag.name, null, enumTypes.enum, enumIcons.tag)),
|
||||
}),
|
||||
],
|
||||
helpString: 'Start a new chat with a random character. If an argument is provided, only considers characters that have the specified tag.',
|
||||
}));
|
||||
@ -3936,9 +3947,13 @@ $(document).ready(() => {
|
||||
callback: doMesCut,
|
||||
returns: 'the text of cut messages separated by a newline',
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'number or range', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'number or range',
|
||||
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE],
|
||||
isRequired: true,
|
||||
acceptsMultiple: true,
|
||||
enumProvider: commonEnumProviders.messages(),
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
|
@ -22,6 +22,8 @@ import { kai_settings } from './kai-settings.js';
|
||||
import { context_presets, getContextSettings, power_user } from './power-user.js';
|
||||
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
|
||||
import { enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
|
||||
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||
import {
|
||||
textgenerationwebui_preset_names,
|
||||
@ -140,7 +142,10 @@ class PresetManager {
|
||||
* @param {string} value Preset option value
|
||||
*/
|
||||
selectPreset(value) {
|
||||
$(this.select).find(`option[value=${value}]`).prop('selected', true);
|
||||
const option = $(this.select).filter(function() {
|
||||
return $(this).val() === value;
|
||||
});
|
||||
option.prop('selected', true);
|
||||
$(this.select).val(value).trigger('change');
|
||||
}
|
||||
|
||||
@ -479,11 +484,12 @@ export async function initPresetManager() {
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'preset',
|
||||
callback: presetCommandCallback,
|
||||
returns: 'current preset',
|
||||
namedArgumentList: [],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'name', [ARGUMENT_TYPE.STRING], false,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: () => getPresetManager().getAllPresets().map(preset => new SlashCommandEnumValue(preset, null, enumTypes.enum, enumIcons.preset)),
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
|
@ -433,6 +433,24 @@ class FandomScraper {
|
||||
}
|
||||
}
|
||||
|
||||
const iso6391Codes = [
|
||||
'aa', 'ab', 'ae', 'af', 'ak', 'am', 'an', 'ar', 'as', 'av', 'ay', 'az',
|
||||
'ba', 'be', 'bg', 'bh', 'bi', 'bm', 'bn', 'bo', 'br', 'bs', 'ca', 'ce',
|
||||
'ch', 'co', 'cr', 'cs', 'cu', 'cv', 'cy', 'da', 'de', 'dv', 'dz', 'ee',
|
||||
'el', 'en', 'eo', 'es', 'et', 'eu', 'fa', 'ff', 'fi', 'fj', 'fo', 'fr',
|
||||
'fy', 'ga', 'gd', 'gl', 'gn', 'gu', 'gv', 'ha', 'he', 'hi', 'ho', 'hr',
|
||||
'ht', 'hu', 'hy', 'hz', 'ia', 'id', 'ie', 'ig', 'ii', 'ik', 'io', 'is',
|
||||
'it', 'iu', 'ja', 'jv', 'ka', 'kg', 'ki', 'kj', 'kk', 'kl', 'km', 'kn',
|
||||
'ko', 'kr', 'ks', 'ku', 'kv', 'kw', 'ky', 'la', 'lb', 'lg', 'li', 'ln',
|
||||
'lo', 'lt', 'lu', 'lv', 'mg', 'mh', 'mi', 'mk', 'ml', 'mn', 'mr', 'ms',
|
||||
'mt', 'my', 'na', 'nb', 'nd', 'ne', 'ng', 'nl', 'nn', 'no', 'nr', 'nv',
|
||||
'ny', 'oc', 'oj', 'om', 'or', 'os', 'pa', 'pi', 'pl', 'ps', 'pt', 'qu',
|
||||
'rm', 'rn', 'ro', 'ru', 'rw', 'sa', 'sc', 'sd', 'se', 'sg', 'si', 'sk',
|
||||
'sl', 'sm', 'sn', 'so', 'sq', 'sr', 'ss', 'st', 'su', 'sv', 'sw', 'ta',
|
||||
'te', 'tg', 'th', 'ti', 'tk', 'tl', 'tn', 'to', 'tr', 'ts', 'tt', 'tw',
|
||||
'ty', 'ug', 'uk', 'ur', 'uz', 've', 'vi', 'vo', 'wa', 'wo', 'xh', 'yi',
|
||||
'yo', 'za', 'zh', 'zu'];
|
||||
|
||||
/**
|
||||
* Scrape transcript from a YouTube video.
|
||||
* @implements {Scraper}
|
||||
@ -464,7 +482,7 @@ class YouTubeScraper {
|
||||
helpString: 'Scrape a transcript from a YouTube video by ID or URL.',
|
||||
returns: ARGUMENT_TYPE.STRING,
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument('lang', 'ISO 639-1 language code of the transcript, e.g. "en"', ARGUMENT_TYPE.STRING, false, false, ''),
|
||||
new SlashCommandNamedArgument('lang', 'ISO 639-1 language code of the transcript, e.g. "en"', ARGUMENT_TYPE.STRING, false, false, '', iso6391Codes),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument('URL or ID of the YouTube video', ARGUMENT_TYPE.STRING, true, false),
|
||||
@ -514,7 +532,7 @@ class YouTubeScraper {
|
||||
}
|
||||
|
||||
const toast = toastr.info('Working, please wait...');
|
||||
const { transcript, id } = await this.getScript(videoUrl, lang);
|
||||
const { transcript, id } = await this.getScript(String(videoUrl), lang);
|
||||
toastr.clear(toast);
|
||||
|
||||
const file = new File([transcript], `YouTube - ${id} - ${Date.now()}.txt`, { type: 'text/plain' });
|
||||
|
@ -42,9 +42,9 @@ import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandPa
|
||||
import { SlashCommandParserError } from './slash-commands/SlashCommandParserError.js';
|
||||
import { getMessageTimeStamp } from './RossAscends-mods.js';
|
||||
import { hideChatMessageRange } from './chats.js';
|
||||
import { getContext, saveMetadataDebounced } from './extensions.js';
|
||||
import { extension_settings, getContext, saveMetadataDebounced } from './extensions.js';
|
||||
import { getRegexedString, regex_placement } from './extensions/regex/engine.js';
|
||||
import { findGroupMemberId, groups, is_group_generating, openGroupById, resetSelectedGroup, saveGroupChat, selected_group } from './group-chats.js';
|
||||
import { findGroupMemberId, getGroupMembers, groups, is_group_generating, openGroupById, resetSelectedGroup, saveGroupChat, selected_group } from './group-chats.js';
|
||||
import { chat_completion_sources, oai_settings, setupChatCompletionPromptManager } from './openai.js';
|
||||
import { autoSelectPersona, retriggerFirstMessageOnEmptyChat, user_avatar } from './personas.js';
|
||||
import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js';
|
||||
@ -61,8 +61,9 @@ import { AutoComplete } from './autocomplete/AutoComplete.js';
|
||||
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||
import { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.js';
|
||||
import { SlashCommandNamedArgumentAssignment } from './slash-commands/SlashCommandNamedArgumentAssignment.js';
|
||||
import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
|
||||
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
|
||||
import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
|
||||
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
import { SlashCommandDebugController } from './slash-commands/SlashCommandDebugController.js';
|
||||
export {
|
||||
executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,
|
||||
@ -79,9 +80,16 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: '?',
|
||||
callback: helpCommandCallback,
|
||||
aliases: ['help'],
|
||||
unnamedArgumentList: [new SlashCommandArgument(
|
||||
'help topic', ARGUMENT_TYPE.STRING, false, false, null, ['slash', 'format', 'hotkeys', 'macros'],
|
||||
)],
|
||||
unnamedArgumentList: [SlashCommandArgument.fromProps({
|
||||
description: 'help topic',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumList: [
|
||||
new SlashCommandEnumValue('slash', 'slash commands (STscript)', enumTypes.command, '/'),
|
||||
new SlashCommandEnumValue('macros', '{{macros}} (text replacement)', enumTypes.macro, enumIcons.macro),
|
||||
new SlashCommandEnumValue('format', 'chat/text formatting', enumTypes.name, '★'),
|
||||
new SlashCommandEnumValue('hotkeys', 'keyboard shortcuts', enumTypes.enum, '⏎'),
|
||||
],
|
||||
})],
|
||||
helpString: 'Get help on macros, chat formatting and commands.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
@ -94,9 +102,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'persona name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'persona name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: commonEnumProviders.personas,
|
||||
}),
|
||||
],
|
||||
helpString: 'Selects the given persona with its name and avatar (by name or avatar url). If no matching persona exists, applies a temporary name.',
|
||||
aliases: ['name'],
|
||||
@ -123,9 +134,8 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: () => [...document.querySelectorAll('.bg_example')]
|
||||
.map((it) => new SlashCommandEnumValue(it.getAttribute('bgfile')))
|
||||
.filter(it => it.value?.length)
|
||||
,
|
||||
.map(it => new SlashCommandEnumValue(it.getAttribute('bgfile')))
|
||||
.filter(it => it.value?.length),
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
@ -146,16 +156,22 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'sendas',
|
||||
callback: sendMessageAs,
|
||||
namedArgumentList: [
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'name',
|
||||
description: 'Character name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: commonEnumProviders.characters('character'),
|
||||
forceEnum: false,
|
||||
}),
|
||||
new SlashCommandNamedArgument(
|
||||
'name', 'Character name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'compact', 'Use compact layout', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', ['true', 'false'],
|
||||
'compact', 'Use compact layout', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'at',
|
||||
description: 'position to insert the message',
|
||||
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
|
||||
enumProvider: commonEnumProviders.messages({ allowIdAfter: true, allowVars: true }),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
@ -198,6 +214,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'at',
|
||||
description: 'position to insert the message',
|
||||
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
|
||||
enumProvider: commonEnumProviders.messages({ allowIdAfter: true, allowVars: true }),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
@ -251,6 +268,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'at',
|
||||
description: 'position to insert the message',
|
||||
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
|
||||
enumProvider: commonEnumProviders.messages({ allowIdAfter: true, allowVars: true }),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
@ -334,10 +352,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
description: 'name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: () => [
|
||||
...characters.map(it => new SlashCommandEnumValue(it.name, null, 'qr', 'C')),
|
||||
...groups.map(it => new SlashCommandEnumValue(it.name, null, 'variable', 'G')),
|
||||
],
|
||||
enumProvider: commonEnumProviders.characters('all'),
|
||||
}),
|
||||
],
|
||||
helpString: 'Opens up a chat with the character or group by its name',
|
||||
@ -385,7 +400,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
description: 'character name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: () => characters.map(it => new SlashCommandEnumValue(it.name, null, 'qr', 'C')),
|
||||
enumProvider: commonEnumProviders.characters('character'),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
@ -400,9 +415,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
callback: deleteMessagesByNameCallback,
|
||||
namedArgumentList: [],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: commonEnumProviders.characters('character'),
|
||||
}),
|
||||
],
|
||||
aliases: ['cancel'],
|
||||
helpString: `
|
||||
@ -435,15 +453,18 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'at',
|
||||
description: 'position to insert the message',
|
||||
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
|
||||
enumProvider: commonEnumProviders.messages({ allowIdAfter: true, allowVars: true }),
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'name',
|
||||
description: 'display name',
|
||||
typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME],
|
||||
defaultValue: '{{user}}',
|
||||
enumProvider: () => [
|
||||
...commonEnumProviders.characters('character')(),
|
||||
...commonEnumProviders.variables('all')().map(x => { x.description = 'Variable'; return x; }),
|
||||
],
|
||||
}),
|
||||
new SlashCommandNamedArgument(
|
||||
'name',
|
||||
'display name',
|
||||
[ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME],
|
||||
false,
|
||||
false,
|
||||
'{{user}}',
|
||||
),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
@ -488,6 +509,14 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
'false',
|
||||
),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'group member index (starts with 0) or name',
|
||||
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING],
|
||||
isRequired: false,
|
||||
enumProvider: commonEnumProviders.groupMembers(),
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
Triggers a message generation. If in group, can trigger a message for the specified group member index or name.
|
||||
@ -501,9 +530,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'hide',
|
||||
callback: hideMessageCallback,
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'message index or range', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'message index (starts with 0) or range',
|
||||
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE],
|
||||
isRequired: true,
|
||||
enumProvider: commonEnumProviders.messages(),
|
||||
}),
|
||||
],
|
||||
helpString: 'Hides a chat message from the prompt.',
|
||||
}));
|
||||
@ -511,9 +543,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'unhide',
|
||||
callback: unhideMessageCallback,
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'message index or range', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'message index (starts with 0) or range',
|
||||
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE],
|
||||
isRequired: true,
|
||||
enumProvider: commonEnumProviders.messages(),
|
||||
}),
|
||||
],
|
||||
helpString: 'Unhides a message from the prompt.',
|
||||
}));
|
||||
@ -522,9 +557,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
callback: disableGroupMemberCallback,
|
||||
aliases: ['disable', 'disablemember', 'memberdisable'],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'member index (starts with 0) or name',
|
||||
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: commonEnumProviders.groupMembers(),
|
||||
}),
|
||||
],
|
||||
helpString: 'Disables a group member from being drafted for replies.',
|
||||
}));
|
||||
@ -533,9 +571,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
aliases: ['enable', 'enablemember', 'memberenable'],
|
||||
callback: enableGroupMemberCallback,
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'member index (starts with 0) or name',
|
||||
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: commonEnumProviders.groupMembers(),
|
||||
}),
|
||||
],
|
||||
helpString: 'Enables a group member to be drafted for replies.',
|
||||
}));
|
||||
@ -544,9 +585,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
callback: addGroupMemberCallback,
|
||||
aliases: ['addmember', 'memberadd'],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'character name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'character name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: () => selected_group ? commonEnumProviders.characters('character')() : [],
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
@ -567,9 +611,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
callback: removeGroupMemberCallback,
|
||||
aliases: ['removemember', 'memberremove'],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'member index (starts with 0) or name',
|
||||
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: commonEnumProviders.groupMembers(),
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
@ -591,9 +638,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
callback: moveGroupMemberUpCallback,
|
||||
aliases: ['upmember', 'memberup'],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'member index (starts with 0) or name',
|
||||
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: commonEnumProviders.groupMembers(),
|
||||
}),
|
||||
],
|
||||
helpString: 'Moves a group member up in the group chat list.',
|
||||
}));
|
||||
@ -602,9 +652,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
callback: moveGroupMemberDownCallback,
|
||||
aliases: ['downmember', 'memberdown'],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'member index (starts with 0) or name',
|
||||
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: commonEnumProviders.groupMembers(),
|
||||
}),
|
||||
],
|
||||
helpString: 'Moves a group member down in the group chat list.',
|
||||
}));
|
||||
@ -612,9 +665,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'peek',
|
||||
callback: peekCallback,
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'message index or range', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], false, true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'member index (starts with 0) or name',
|
||||
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: commonEnumProviders.groupMembers(),
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
@ -624,12 +680,8 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
<strong>Examples:</strong>
|
||||
<ul>
|
||||
<li>
|
||||
<pre><code>/peek 5</code></pre>
|
||||
Shows the character card for the 5th message.
|
||||
</li>
|
||||
<li>
|
||||
<pre><code>/peek 2-5</code></pre>
|
||||
Shows the character cards for messages 2 through 5.
|
||||
<pre><code>/peek Gloria</code></pre>
|
||||
Shows the character card for the character named "Gloria".
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@ -640,9 +692,14 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
callback: deleteSwipeCallback,
|
||||
aliases: ['swipedel'],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'1-based swipe id', [ARGUMENT_TYPE.NUMBER],
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: '1-based swipe id',
|
||||
typeList: [ARGUMENT_TYPE.NUMBER],
|
||||
isRequired: true,
|
||||
enumProvider: () => Array.isArray(chat[chat.length - 1]?.swipes) ?
|
||||
chat[chat.length - 1].swipes.map((/** @type {string} */ swipe, /** @type {number} */ i) => new SlashCommandEnumValue(String(i + 1), swipe, enumTypes.enum, enumIcons.message))
|
||||
: [],
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
@ -671,9 +728,18 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
new SlashCommandNamedArgument(
|
||||
'title', 'title of the toast message', [ARGUMENT_TYPE.STRING], false,
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'severity', 'severity level of the toast message', [ARGUMENT_TYPE.STRING], false, false, null, ['info', 'warning', 'error', 'success'],
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'severity',
|
||||
description: 'severity level of the toast message',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
defaultValue: 'info',
|
||||
enumProvider: () => [
|
||||
new SlashCommandEnumValue('info', 'info', enumTypes.macro, 'ℹ️'),
|
||||
new SlashCommandEnumValue('warning', 'warning', enumTypes.enum, '⚠️'),
|
||||
new SlashCommandEnumValue('error', 'error', enumTypes.enum, '❗'),
|
||||
new SlashCommandEnumValue('success', 'success', enumTypes.enum, '✅'),
|
||||
],
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
@ -700,17 +766,28 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
returns: 'generated text',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'lock', 'lock user input during generation', [ARGUMENT_TYPE.BOOLEAN], false, false, null, ['on', 'off'],
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'name', 'in-prompt name for instruct mode', [ARGUMENT_TYPE.STRING], false, false, 'System',
|
||||
'lock', 'lock user input during generation', [ARGUMENT_TYPE.BOOLEAN], false, false, null, commonEnumProviders.boolean('onOff')(),
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'name',
|
||||
description: 'in-prompt name for instruct mode',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
defaultValue: 'System',
|
||||
enumProvider: () => [...commonEnumProviders.characters('character')(), new SlashCommandEnumValue('System', null, enumTypes.enum, enumIcons.assistant)],
|
||||
forceEnum: false,
|
||||
}),
|
||||
new SlashCommandNamedArgument(
|
||||
'length', 'API response length in tokens', [ARGUMENT_TYPE.NUMBER], false,
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'as', 'role of the output prompt', [ARGUMENT_TYPE.STRING], false, false, 'system', ['system', 'char'],
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'as',
|
||||
description: 'role of the output prompt',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumList: [
|
||||
new SlashCommandEnumValue('system', null, enumTypes.enum, enumIcons.assistant),
|
||||
new SlashCommandEnumValue('char', null, enumTypes.enum, enumIcons.character),
|
||||
],
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
@ -732,17 +809,23 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
returns: 'generated text',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'lock', 'lock user input during generation', [ARGUMENT_TYPE.BOOLEAN], false, false, null, ['on', 'off'],
|
||||
'lock', 'lock user input during generation', [ARGUMENT_TYPE.BOOLEAN], false, false, null, commonEnumProviders.boolean('onOff')(),
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'instruct', 'use instruct mode', [ARGUMENT_TYPE.BOOLEAN], false, false, 'on', ['on', 'off'],
|
||||
'instruct', 'use instruct mode', [ARGUMENT_TYPE.BOOLEAN], false, false, 'on', commonEnumProviders.boolean('onOff')(),
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'stop', 'one-time custom stop strings', [ARGUMENT_TYPE.LIST], false,
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'as', 'role of the output prompt', [ARGUMENT_TYPE.STRING], false, false, 'system', ['system', 'char'],
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'as',
|
||||
description: 'role of the output prompt',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumList: [
|
||||
new SlashCommandEnumValue('system', null, enumTypes.enum, enumIcons.assistant),
|
||||
new SlashCommandEnumValue('char', null, enumTypes.enum, enumIcons.character),
|
||||
],
|
||||
}),
|
||||
new SlashCommandNamedArgument(
|
||||
'system', 'system prompt at the start', [ARGUMENT_TYPE.STRING], false,
|
||||
),
|
||||
@ -793,7 +876,6 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
description: 'Whether to suppress the toast message notifying about the /abort call.',
|
||||
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||
defaultValue: 'true',
|
||||
enumList: ['true', 'false'],
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
@ -846,7 +928,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'pass',
|
||||
callback: (_, arg) => arg,
|
||||
callback: (_, arg) => {
|
||||
// We do not support arrays of closures. Arrays of strings will be send as JSON
|
||||
if (Array.isArray(arg) && arg.some(x => x instanceof SlashCommandClosure)) throw new Error('Command /pass does not support multiple closures');
|
||||
if (Array.isArray(arg)) return JSON.stringify(arg);
|
||||
return arg;
|
||||
},
|
||||
returns: 'the provided value',
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
@ -899,10 +986,10 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
'default', 'default value of the input field', [ARGUMENT_TYPE.STRING], false, false, '"string"',
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'large', 'show large input field', [ARGUMENT_TYPE.BOOLEAN], false, false, 'off', ['on', 'off'],
|
||||
'large', 'show large input field', [ARGUMENT_TYPE.BOOLEAN], false, false, 'off', commonEnumProviders.boolean('onOff')(),
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'wide', 'show wide input field', [ARGUMENT_TYPE.BOOLEAN], false, false, 'off', ['on', 'off'],
|
||||
'wide', 'show wide input field', [ARGUMENT_TYPE.BOOLEAN], false, false, 'off', commonEnumProviders.boolean('onOff')(),
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'okButton', 'text for the ok button', [ARGUMENT_TYPE.STRING], false,
|
||||
@ -934,9 +1021,15 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'scoped variable or qr label', [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'scoped variable or qr label',
|
||||
typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: () => [
|
||||
...commonEnumProviders.variables('scope')(),
|
||||
...(typeof window['qrEnumProviderExecutables'] === 'function') ? window['qrEnumProviderExecutables']() : [],
|
||||
],
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
@ -951,19 +1044,29 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
aliases: ['message'],
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'names', 'show message author names', [ARGUMENT_TYPE.BOOLEAN], false, false, 'off', ['off', 'on'],
|
||||
'names', 'show message author names', [ARGUMENT_TYPE.BOOLEAN], false, false, 'off', commonEnumProviders.boolean('onOff')(),
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'hidden', 'include hidden messages', [ARGUMENT_TYPE.BOOLEAN], false, false, 'on', ['off', 'on'],
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'role', 'filter messages by role', [ARGUMENT_TYPE.STRING], false, false, null, ['system', 'assistant', 'user'],
|
||||
'hidden', 'include hidden messages', [ARGUMENT_TYPE.BOOLEAN], false, false, 'on', commonEnumProviders.boolean('onOff')(),
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'role',
|
||||
description: 'filter messages by role',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumList: [
|
||||
new SlashCommandEnumValue('system', null, enumTypes.enum, enumIcons.system),
|
||||
new SlashCommandEnumValue('assistant', null, enumTypes.enum, enumIcons.assistant),
|
||||
new SlashCommandEnumValue('user', null, enumTypes.enum, enumIcons.user),
|
||||
],
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'message index or range', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], false, true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'message index (starts with 0) or range',
|
||||
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE],
|
||||
isRequired: true,
|
||||
enumProvider: commonEnumProviders.messages(),
|
||||
}),
|
||||
],
|
||||
returns: 'the specified message or range of messages as a string',
|
||||
helpString: `
|
||||
@ -1019,10 +1122,10 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
returns: 'popup text',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'large', 'show large popup', [ARGUMENT_TYPE.BOOLEAN], false, false, null, ['on', 'off'],
|
||||
'large', 'show large popup', [ARGUMENT_TYPE.BOOLEAN], false, false, null, commonEnumProviders.boolean('onOff')(),
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'wide', 'show wide popup', [ARGUMENT_TYPE.BOOLEAN], false, false, null, ['on', 'off'],
|
||||
'wide', 'show wide popup', [ARGUMENT_TYPE.BOOLEAN], false, false, null, commonEnumProviders.boolean('onOff')(),
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'okButton', 'text for the OK button', [ARGUMENT_TYPE.STRING], false,
|
||||
@ -1085,9 +1188,16 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
new SlashCommandNamedArgument(
|
||||
'limit', 'number of tokens to keep', [ARGUMENT_TYPE.NUMBER], true,
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'direction', 'trim direction', [ARGUMENT_TYPE.STRING], true, false, null, ['start', 'end'],
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'direction',
|
||||
description: 'trim direction',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumList: [
|
||||
new SlashCommandEnumValue('start', null, enumTypes.enum, '⏪'),
|
||||
new SlashCommandEnumValue('end', null, enumTypes.enum, '⏩'),
|
||||
],
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
@ -1146,9 +1256,17 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'inject',
|
||||
callback: injectCallback,
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'id', 'injection ID', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'id',
|
||||
description: 'injection ID or variable name pointing to ID',
|
||||
typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME],
|
||||
isRequired: true,
|
||||
|
||||
enumProvider: () => [
|
||||
...commonEnumProviders.injects(),
|
||||
...commonEnumProviders.variables('all')().map(x => { x.description = 'Variable'; return x; }),
|
||||
],
|
||||
}),
|
||||
new SlashCommandNamedArgument(
|
||||
'position', 'injection position', [ARGUMENT_TYPE.STRING], false, false, 'after', ['before', 'after', 'chat'],
|
||||
),
|
||||
@ -1158,11 +1276,19 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
new SlashCommandNamedArgument(
|
||||
'scan', 'include injection content into World Info scans', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'role',
|
||||
description: 'role for in-chat injections',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: false,
|
||||
enumList: [
|
||||
new SlashCommandEnumValue('system', null, enumTypes.enum, enumIcons.system),
|
||||
new SlashCommandEnumValue('assistant', null, enumTypes.enum, enumIcons.assistant),
|
||||
new SlashCommandEnumValue('user', null, enumTypes.enum, enumIcons.user),
|
||||
],
|
||||
}),
|
||||
new SlashCommandNamedArgument(
|
||||
'role', 'role for in-chat injections', [ARGUMENT_TYPE.STRING], false, false, 'system', ['system', 'user', 'assistant'],
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'ephemeral', 'remove injection after generation', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', ['true', 'false'],
|
||||
'ephemeral', 'remove injection after generation', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',
|
||||
),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
@ -1181,16 +1307,25 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'flushinject',
|
||||
aliases: ['flushinjects'],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'injection ID or a variable name pointing to ID', [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME], false, false, '',
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'injection ID or a variable name pointing to ID',
|
||||
typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME],
|
||||
defaultValue: '',
|
||||
enumProvider: () => [
|
||||
...commonEnumProviders.injects(),
|
||||
...commonEnumProviders.variables('all')().map(x => { x.description = 'Variable'; return x; }),
|
||||
],
|
||||
}),
|
||||
],
|
||||
callback: flushInjectsCallback,
|
||||
helpString: 'Removes a script injection for the current chat. If no ID is provided, removes all script injections.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'tokens',
|
||||
callback: (_, text) => getTokenCountAsync(text),
|
||||
callback: (_, text) => {
|
||||
if (text instanceof SlashCommandClosure || Array.isArray(text)) throw new Error('Unnamed argument cannot be a closure for command /tokens');
|
||||
return getTokenCountAsync(text).then(count => String(count));
|
||||
},
|
||||
returns: 'number of tokens',
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
@ -1204,9 +1339,11 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
callback: modelCallback,
|
||||
returns: 'current model',
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'model name', [ARGUMENT_TYPE.STRING], false,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'model name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: () => getModelOptions()?.options.map(option => new SlashCommandEnumValue(option.value, option.value !== option.text ? option.text : null)),
|
||||
}),
|
||||
],
|
||||
helpString: 'Sets the model for the current API. Gets the current model name if no argument is provided.',
|
||||
}));
|
||||
@ -1215,12 +1352,28 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
aliases: ['setpromptentries'],
|
||||
callback: setPromptEntryCallback,
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'identifier', 'Prompt entry identifier(s) to target', [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST], false, true,
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'name', 'Prompt entry name(s) to target', [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST], false, true,
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'identifier',
|
||||
description: 'Prompt entry identifier(s) to target',
|
||||
typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST],
|
||||
acceptsMultiple: true,
|
||||
enumProvider: () => {
|
||||
const promptManager = setupChatCompletionPromptManager(oai_settings);
|
||||
const prompts = promptManager.serviceSettings.prompts;
|
||||
return prompts.map(prompt => new SlashCommandEnumValue(prompt.identifier, prompt.name, enumTypes.enum));
|
||||
},
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'name',
|
||||
description: 'Prompt entry name(s) to target',
|
||||
typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST],
|
||||
acceptsMultiple: true,
|
||||
enumProvider: () => {
|
||||
const promptManager = setupChatCompletionPromptManager(oai_settings);
|
||||
const prompts = promptManager.serviceSettings.prompts;
|
||||
return prompts.map(prompt => new SlashCommandEnumValue(prompt.name, prompt.identifier, enumTypes.enum));
|
||||
},
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
@ -1229,7 +1382,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
isRequired: true,
|
||||
acceptsMultiple: false,
|
||||
defaultValue: 'toggle', // unnamed arguments don't support default values yet
|
||||
enumList: ['on', 'off', 'toggle'],
|
||||
enumList: commonEnumProviders.boolean('onOffToggle')(),
|
||||
}),
|
||||
],
|
||||
helpString: 'Sets the specified prompt manager entry/entries on or off.',
|
||||
@ -1506,7 +1659,7 @@ async function popupCallback(args, value) {
|
||||
await delay(1);
|
||||
await callGenericPopup(safeValue, POPUP_TYPE.TEXT, '', popupOptions);
|
||||
await delay(1);
|
||||
return value;
|
||||
return String(value);
|
||||
}
|
||||
|
||||
function getMessagesCallback(args, value) {
|
||||
@ -1610,13 +1763,13 @@ async function runCallback(args, name) {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} param0
|
||||
* @param {SlashCommandAbortController} param0._abortController
|
||||
* @param {string} [param0.quiet]
|
||||
* @param {import('./slash-commands/SlashCommand.js').NamedArguments} param0
|
||||
* @param {string} [reason]
|
||||
*/
|
||||
function abortCallback({ _abortController, quiet }, reason) {
|
||||
if (quiet instanceof SlashCommandClosure) throw new Error('argument \'quiet\' cannot be a closure for command /abort');
|
||||
_abortController.abort((reason ?? '').toString().length == 0 ? '/abort command executed' : reason, !isFalseBoolean(quiet ?? 'true'));
|
||||
return '';
|
||||
}
|
||||
|
||||
async function delayCallback(_, amount) {
|
||||
@ -1645,9 +1798,9 @@ async function inputCallback(args, prompt) {
|
||||
};
|
||||
// Do not remove this delay, otherwise the prompt will not show up
|
||||
await delay(1);
|
||||
const result = await callPopup(safeValue, 'input', defaultInput, popupOptions);
|
||||
const result = await callGenericPopup(safeValue, POPUP_TYPE.INPUT, defaultInput, popupOptions);
|
||||
await delay(1);
|
||||
return result || '';
|
||||
return String(result || '');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2767,12 +2920,11 @@ function setBackgroundCallback(_, bg) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a model for the current API.
|
||||
* @param {object} _ Unused
|
||||
* @param {string} model New model name
|
||||
* @returns {string} New or existing model name
|
||||
* Retrieves the available model options based on the currently selected main API and its subtype
|
||||
*
|
||||
* @returns {{control: HTMLSelectElement, options: HTMLOptionElement[]}?} An array of objects representing the available model options, or null if not supported
|
||||
*/
|
||||
function modelCallback(_, model) {
|
||||
function getModelOptions() {
|
||||
const modelSelectMap = [
|
||||
{ id: 'model_togetherai_select', api: 'textgenerationwebui', type: textgen_types.TOGETHERAI },
|
||||
{ id: 'openrouter_model', api: 'textgenerationwebui', type: textgen_types.OPENROUTER },
|
||||
@ -2813,17 +2965,33 @@ function modelCallback(_, model) {
|
||||
|
||||
if (!modelSelectItem) {
|
||||
toastr.info('Setting a model for your API is not supported or not implemented yet.');
|
||||
return '';
|
||||
return null;
|
||||
}
|
||||
|
||||
const modelSelectControl = document.getElementById(modelSelectItem);
|
||||
|
||||
if (!(modelSelectControl instanceof HTMLSelectElement)) {
|
||||
toastr.error(`Model select control not found: ${main_api}[${apiSubType}]`);
|
||||
return '';
|
||||
return null;
|
||||
}
|
||||
|
||||
const options = Array.from(modelSelectControl.options);
|
||||
return { control: modelSelectControl, options };
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a model for the current API.
|
||||
* @param {object} _ Unused
|
||||
* @param {string} model New model name
|
||||
* @returns {string} New or existing model name
|
||||
*/
|
||||
function modelCallback(_, model) {
|
||||
const { control: modelSelectControl, options } = getModelOptions();
|
||||
|
||||
// If no model was found, the reason was already logged, we just return here
|
||||
if (options === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!options.length) {
|
||||
toastr.warning('No model options found. Check your API settings.');
|
||||
|
@ -175,6 +175,11 @@ export class SlashCommand {
|
||||
}
|
||||
li.append(specs);
|
||||
}
|
||||
const stopgap = document.createElement('span'); {
|
||||
stopgap.classList.add('stopgap');
|
||||
stopgap.textContent = '';
|
||||
li.append(stopgap);
|
||||
}
|
||||
const help = document.createElement('span'); {
|
||||
help.classList.add('help');
|
||||
const content = document.createElement('span'); {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { SlashCommandClosure } from './SlashCommandClosure.js';
|
||||
import { commonEnumProviders } from './SlashCommandCommonEnumsProvider.js';
|
||||
import { SlashCommandEnumValue } from './SlashCommandEnumValue.js';
|
||||
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
||||
import { SlashCommandScope } from './SlashCommandScope.js';
|
||||
@ -19,20 +20,18 @@ export const ARGUMENT_TYPE = {
|
||||
'DICTIONARY': 'dictionary',
|
||||
};
|
||||
|
||||
|
||||
|
||||
export class SlashCommandArgument {
|
||||
/**
|
||||
* Creates an unnamed argument from a properties object.
|
||||
* @param {Object} props
|
||||
* @param {string} props.description description of the argument
|
||||
* @param {ARGUMENT_TYPE|ARGUMENT_TYPE[]} props.typeList default: ARGUMENT_TYPE.STRING - list of accepted types (from ARGUMENT_TYPE)
|
||||
* @param {boolean} [props.isRequired] default: false - whether the argument is required (false = optional argument)
|
||||
* @param {boolean} [props.acceptsMultiple] default: false - whether argument accepts multiple values
|
||||
* @param {string|SlashCommandClosure} [props.defaultValue] default value if no value is provided
|
||||
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [props.enumList] list of accepted values
|
||||
* @param {(executor:SlashCommandExecutor, scope:SlashCommandScope)=>SlashCommandEnumValue[]} [props.enumProvider] function that returns auto complete options
|
||||
* @param {boolean} [props.forceEnum] default: true - whether the input must match one of the enum values
|
||||
* @param {ARGUMENT_TYPE|ARGUMENT_TYPE[]} [props.typeList=[ARGUMENT_TYPE.STRING]] default: ARGUMENT_TYPE.STRING - list of accepted types (from ARGUMENT_TYPE)
|
||||
* @param {boolean} [props.isRequired=false] default: false - whether the argument is required (false = optional argument)
|
||||
* @param {boolean} [props.acceptsMultiple=false] default: false - whether argument accepts multiple values
|
||||
* @param {string|SlashCommandClosure} [props.defaultValue=null] default value if no value is provided
|
||||
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [props.enumList=[]] list of accepted values
|
||||
* @param {(executor:SlashCommandExecutor, scope:SlashCommandScope)=>SlashCommandEnumValue[]} [props.enumProvider=null] function that returns auto complete options
|
||||
* @param {boolean} [props.forceEnum=false] default: false - whether the input must match one of the enum values
|
||||
*/
|
||||
static fromProps(props) {
|
||||
return new SlashCommandArgument(
|
||||
@ -43,13 +42,10 @@ export class SlashCommandArgument {
|
||||
props.defaultValue ?? null,
|
||||
props.enumList ?? [],
|
||||
props.enumProvider ?? null,
|
||||
props.forceEnum ?? true,
|
||||
props.forceEnum ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**@type {string}*/ description;
|
||||
/**@type {ARGUMENT_TYPE[]}*/ typeList = [];
|
||||
/**@type {boolean}*/ isRequired = false;
|
||||
@ -57,8 +53,7 @@ export class SlashCommandArgument {
|
||||
/**@type {string|SlashCommandClosure}*/ defaultValue;
|
||||
/**@type {SlashCommandEnumValue[]}*/ enumList = [];
|
||||
/**@type {(executor:SlashCommandExecutor, scope:SlashCommandScope)=>SlashCommandEnumValue[]}*/ enumProvider = null;
|
||||
/**@type {boolean}*/ forceEnum = true;
|
||||
|
||||
/**@type {boolean}*/ forceEnum = false;
|
||||
|
||||
/**
|
||||
* @param {string} description
|
||||
@ -79,25 +74,26 @@ export class SlashCommandArgument {
|
||||
});
|
||||
this.enumProvider = enumProvider;
|
||||
this.forceEnum = forceEnum;
|
||||
|
||||
// If no enums were set explictly and the type is one where we know possible enum values, we set them here
|
||||
if (!this.enumList.length && this.typeList.includes(ARGUMENT_TYPE.BOOLEAN)) this.enumList = commonEnumProviders.boolean()();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class SlashCommandNamedArgument extends SlashCommandArgument {
|
||||
/**
|
||||
* Creates an unnamed argument from a properties object.
|
||||
* @param {Object} props
|
||||
* @param {string} props.name the argument's name
|
||||
* @param {string[]} [props.aliasList] list of aliases
|
||||
* @param {string} props.description description of the argument
|
||||
* @param {ARGUMENT_TYPE|ARGUMENT_TYPE[]} props.typeList default: ARGUMENT_TYPE.STRING - list of accepted types (from ARGUMENT_TYPE)
|
||||
* @param {boolean} [props.isRequired] default: false - whether the argument is required (false = optional argument)
|
||||
* @param {boolean} [props.acceptsMultiple] default: false - whether argument accepts multiple values
|
||||
* @param {string|SlashCommandClosure} [props.defaultValue] default value if no value is provided
|
||||
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [props.enumList] list of accepted values
|
||||
* @param {(executor:SlashCommandExecutor, scope:SlashCommandScope)=>SlashCommandEnumValue[]} [props.enumProvider] function that returns auto complete options
|
||||
* @param {boolean} [props.forceEnum] default: true - whether the input must match one of the enum values
|
||||
* @param {string[]} [props.aliasList=[]] list of aliases
|
||||
* @param {ARGUMENT_TYPE|ARGUMENT_TYPE[]} [props.typeList=[ARGUMENT_TYPE.STRING]] default: ARGUMENT_TYPE.STRING - list of accepted types (from ARGUMENT_TYPE)
|
||||
* @param {boolean} [props.isRequired=false] default: false - whether the argument is required (false = optional argument)
|
||||
* @param {boolean} [props.acceptsMultiple=false] default: false - whether argument accepts multiple values
|
||||
* @param {string|SlashCommandClosure} [props.defaultValue=null] default value if no value is provided
|
||||
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [props.enumList=[]] list of accepted values
|
||||
* @param {(executor:SlashCommandExecutor, scope:SlashCommandScope)=>SlashCommandEnumValue[]} [props.enumProvider=null] function that returns auto complete options
|
||||
* @param {boolean} [props.forceEnum=true] default: true - whether the input must match one of the enum values
|
||||
*/
|
||||
static fromProps(props) {
|
||||
return new SlashCommandNamedArgument(
|
||||
@ -114,21 +110,20 @@ export class SlashCommandNamedArgument extends SlashCommandArgument {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**@type {string}*/ name;
|
||||
/**@type {string[]}*/ aliasList = [];
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string} description
|
||||
* @param {ARGUMENT_TYPE|ARGUMENT_TYPE[]} types
|
||||
* @param {string|SlashCommandClosure} defaultValue
|
||||
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} enums
|
||||
* @param {(executor:SlashCommandExecutor, scope:SlashCommandScope)=>SlashCommandEnumValue[]} enumProvider function that returns auto complete options
|
||||
* @param {boolean} forceEnum
|
||||
* @param {boolean} [isRequired=false]
|
||||
* @param {boolean} [acceptsMultiple=false]
|
||||
* @param {string|SlashCommandClosure} [defaultValue=null]
|
||||
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [enums=[]]
|
||||
* @param {string[]} [aliases=[]]
|
||||
* @param {(executor:SlashCommandExecutor, scope:SlashCommandScope)=>SlashCommandEnumValue[]} [enumProvider=null] function that returns auto complete options
|
||||
* @param {boolean} [forceEnum=true]
|
||||
*/
|
||||
constructor(name, description, types, isRequired = false, acceptsMultiple = false, defaultValue = null, enums = [], aliases = [], enumProvider = null, forceEnum = true) {
|
||||
super(description, types, isRequired, acceptsMultiple, defaultValue, enums, enumProvider, forceEnum);
|
||||
|
232
public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js
Normal file
232
public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js
Normal file
@ -0,0 +1,232 @@
|
||||
import { chat_metadata, characters, substituteParams, chat, extension_prompt_roles, extension_prompt_types } from "../../script.js";
|
||||
import { extension_settings } from "../extensions.js";
|
||||
import { getGroupMembers, groups, selected_group } from "../group-chats.js";
|
||||
import { power_user } from "../power-user.js";
|
||||
import { searchCharByName, getTagsList, tags } from "../tags.js";
|
||||
import { SlashCommandClosure } from "./SlashCommandClosure.js";
|
||||
import { SlashCommandEnumValue, enumTypes } from "./SlashCommandEnumValue.js";
|
||||
import { SlashCommandExecutor } from "./SlashCommandExecutor.js";
|
||||
|
||||
/**
|
||||
* A collection of regularly used enum icons
|
||||
*/
|
||||
export const enumIcons = {
|
||||
default: '◊',
|
||||
|
||||
// Variables
|
||||
variable: '𝑥',
|
||||
localVariable: 'L',
|
||||
globalVariable: 'G',
|
||||
scopeVariable: 'S',
|
||||
|
||||
// Common types
|
||||
character: '👤',
|
||||
group: '🧑🤝🧑',
|
||||
persona: '🧙♂️',
|
||||
qr: 'QR',
|
||||
closure: '𝑓',
|
||||
macro: '{{',
|
||||
tag: '🏷️',
|
||||
world: '🌐',
|
||||
preset: '⚙️',
|
||||
file: '📄',
|
||||
message: '💬',
|
||||
voice: '🎤',
|
||||
|
||||
true: '✔️',
|
||||
false: '❌',
|
||||
|
||||
// Value types
|
||||
boolean: '🔲',
|
||||
string: '📝',
|
||||
number: '1️⃣',
|
||||
array: '[]',
|
||||
enum: '📚',
|
||||
dictionary: '{}',
|
||||
|
||||
// Roles
|
||||
system: '⚙️',
|
||||
user: '👤',
|
||||
assistant: '🤖',
|
||||
|
||||
// WI Icons
|
||||
constant: '🔵',
|
||||
normal: '🟢',
|
||||
disabled: '❌',
|
||||
vectorized: '🔗',
|
||||
|
||||
/**
|
||||
* Returns the appropriate state icon based on a boolean
|
||||
*
|
||||
* @param {boolean} state - The state to determine the icon for
|
||||
* @returns {string} The corresponding state icon
|
||||
*/
|
||||
getStateIcon: (state) => {
|
||||
return state ? enumIcons.true : enumIcons.false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the appropriate WI icon based on the entry
|
||||
*
|
||||
* @param {Object} entry - WI entry
|
||||
* @returns {string} The corresponding WI icon
|
||||
*/
|
||||
getWiStatusIcon: (entry) => {
|
||||
if (entry.constant) return enumIcons.constant;
|
||||
if (entry.disable) return enumIcons.disabled;
|
||||
if (entry.vectorized) return enumIcons.vectorized;
|
||||
return enumIcons.normal;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the appropriate icon based on the role
|
||||
*
|
||||
* @param {extension_prompt_roles} role - The role to get the icon for
|
||||
* @returns {string} The corresponding icon
|
||||
*/
|
||||
getRoleIcon: (role) => {
|
||||
switch (role) {
|
||||
case extension_prompt_roles.SYSTEM: return enumIcons.system;
|
||||
case extension_prompt_roles.USER: return enumIcons.user;
|
||||
case extension_prompt_roles.ASSISTANT: return enumIcons.assistant;
|
||||
default: return enumIcons.default;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* A function to get the data type icon
|
||||
*
|
||||
* @param {string} type - The type of the data
|
||||
* @returns {string} The corresponding data type icon
|
||||
*/
|
||||
getDataTypeIcon: (type) => {
|
||||
// Remove possible nullable types definition to match type icon
|
||||
type = type.replace(/\?$/, '');
|
||||
return enumIcons[type] ?? enumIcons.default;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A collection of common enum providers
|
||||
*
|
||||
* Can be used on `SlashCommandNamedArgument` and `SlashCommandArgument` and their `enumProvider` property.
|
||||
*/
|
||||
export const commonEnumProviders = {
|
||||
/**
|
||||
* Enum values for booleans. Either using true/false or on/off
|
||||
* Optionally supports "toggle".
|
||||
*
|
||||
* @param {('onOff'|'onOffToggle'|'trueFalse')?} [mode='trueFalse'] - The mode to use. Default is 'trueFalse'.
|
||||
* @returns {() => SlashCommandEnumValue[]}
|
||||
*/
|
||||
boolean: (mode = 'trueFalse') => () => {
|
||||
switch (mode) {
|
||||
case 'onOff': return [new SlashCommandEnumValue('on', null, 'macro', enumIcons.true), new SlashCommandEnumValue('off', null, 'macro', enumIcons.false)];
|
||||
case 'onOffToggle': return [new SlashCommandEnumValue('on', null, 'macro', enumIcons.true), new SlashCommandEnumValue('off', null, 'macro', enumIcons.false), new SlashCommandEnumValue('toggle', null, 'macro', enumIcons.boolean)];
|
||||
case 'trueFalse': return [new SlashCommandEnumValue('true', null, 'macro', enumIcons.true), new SlashCommandEnumValue('false', null, 'macro', enumIcons.false)];
|
||||
default: throw new Error(`Invalid boolean enum provider mode: ${mode}`);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* All possible variable names
|
||||
*
|
||||
* Can be filtered by `type` to only show global or local variables
|
||||
*
|
||||
* @param {...('global'|'local'|'scope'|'all')} type - The type of variables to include in the array. Can be 'all', 'global', or 'local'.
|
||||
* @returns {() => SlashCommandEnumValue[]}
|
||||
*/
|
||||
variables: (...type) => () => {
|
||||
const types = type.flat();
|
||||
const isAll = types.includes('all');
|
||||
return [
|
||||
...isAll || types.includes('global') ? Object.keys(extension_settings.variables.global ?? []).map(name => new SlashCommandEnumValue(name, null, enumTypes.macro, enumIcons.globalVariable)) : [],
|
||||
...isAll || types.includes('local') ? Object.keys(chat_metadata.variables ?? []).map(name => new SlashCommandEnumValue(name, null, enumTypes.name, enumIcons.localVariable)) : [],
|
||||
...isAll || types.includes('scope') ? [].map(name => new SlashCommandEnumValue(name, null, enumTypes.variable, enumIcons.scopeVariable)) : [], // TODO: Add scoped variables here, Lenny
|
||||
]
|
||||
},
|
||||
|
||||
/**
|
||||
* All possible char entities, like characters and groups. Can be filtered down to just one type.
|
||||
*
|
||||
* @param {('all' | 'character' | 'group')?} [mode='all'] - Which type to return
|
||||
* @returns {() => SlashCommandEnumValue[]}
|
||||
*/
|
||||
characters: (mode = 'all') => () => {
|
||||
return [
|
||||
...['all', 'character'].includes(mode) ? characters.map(char => new SlashCommandEnumValue(char.name, null, enumTypes.name, enumIcons.character)) : [],
|
||||
...['all', 'group'].includes(mode) ? groups.map(group => new SlashCommandEnumValue(group.name, null, enumTypes.qr, enumIcons.group)) : [],
|
||||
];
|
||||
},
|
||||
|
||||
/**
|
||||
* All group members of the given group, or default the current active one
|
||||
*
|
||||
* @param {string?} groupId - The id of the group - pass in `undefined` to use the current active group
|
||||
* @returns {() =>SlashCommandEnumValue[]}
|
||||
*/
|
||||
groupMembers: (groupId = undefined) => () => getGroupMembers(groupId).map((character, index) => new SlashCommandEnumValue(String(index), character.name, enumTypes.enum, enumIcons.character)),
|
||||
|
||||
/**
|
||||
* All possible personas
|
||||
*
|
||||
* @returns {SlashCommandEnumValue[]}
|
||||
*/
|
||||
personas: () => Object.values(power_user.personas).map(persona => new SlashCommandEnumValue(persona, null, enumTypes.name, enumIcons.persona)),
|
||||
|
||||
/**
|
||||
* All possible tags for a given char/group entity
|
||||
*
|
||||
* @param {('all' | 'existing' | 'not-existing')?} [mode='all'] - Which types of tags to show
|
||||
* @returns {() => SlashCommandEnumValue[]}
|
||||
*/
|
||||
tagsForChar: (mode = 'all') => (/** @type {SlashCommandExecutor} */ executor) => {
|
||||
// Try to see if we can find the char during execution to filter down the tags list some more. Otherwise take all tags.
|
||||
const charName = executor.namedArgumentList.find(it => it.name == 'name')?.value;
|
||||
if (charName instanceof SlashCommandClosure) throw new Error('Argument \'name\' does not support closures');
|
||||
const key = searchCharByName(substituteParams(charName), { suppressLogging: true });
|
||||
const assigned = key ? getTagsList(key) : [];
|
||||
return tags.filter(it => !key || mode === 'all' || mode === 'existing' && assigned.includes(it) || mode === 'not-existing' && !assigned.includes(it))
|
||||
.map(tag => new SlashCommandEnumValue(tag.name, null, enumTypes.command, enumIcons.tag));
|
||||
},
|
||||
|
||||
/**
|
||||
* All messages in the current chat, returning the message id
|
||||
*
|
||||
* Optionally supports variable names, and/or a placeholder for the last/new message id
|
||||
*
|
||||
* @param {object} [options={}] - Optional arguments
|
||||
* @param {boolean} [options.allowIdAfter=false] - Whether to add an enum option for the new message id after the last message
|
||||
* @param {boolean} [options.allowVars=false] - Whether to add enum option for variable names
|
||||
* @returns {() => SlashCommandEnumValue[]}
|
||||
*/
|
||||
messages: ({ allowIdAfter = false, allowVars = false } = {}) => () => {
|
||||
return [
|
||||
...chat.map((message, index) => new SlashCommandEnumValue(String(index), `${message.name}: ${message.mes}`, enumTypes.number, message.is_user ? enumIcons.user : message.is_system ? enumIcons.system : enumIcons.assistant)),
|
||||
...allowIdAfter ? [new SlashCommandEnumValue(String(chat.length), '>> After Last Message >>', enumTypes.enum, '➕')] : [],
|
||||
...allowVars ? commonEnumProviders.variables('all')() : [],
|
||||
];
|
||||
},
|
||||
|
||||
/**
|
||||
* All existing worlds / lorebooks
|
||||
*
|
||||
* @returns {SlashCommandEnumValue[]}
|
||||
*/
|
||||
worlds: () => $('#world_info').children().toArray().map(x => new SlashCommandEnumValue(x.textContent, null, enumTypes.name, enumIcons.world)),
|
||||
|
||||
/**
|
||||
* All existing injects for the current chat
|
||||
*
|
||||
* @returns {SlashCommandEnumValue[]}
|
||||
*/
|
||||
injects: () => {
|
||||
if (!chat_metadata.script_injects || !Object.keys(chat_metadata.script_injects).length) return [];
|
||||
return Object.entries(chat_metadata.script_injects)
|
||||
.map(([id, inject]) => {
|
||||
const positionName = (Object.entries(extension_prompt_types)).find(([_, value]) => value === inject.position)?.[0] ?? 'unknown';
|
||||
return new SlashCommandEnumValue(id, `${enumIcons.getRoleIcon(inject.role ?? extension_prompt_roles.SYSTEM)}[Inject](${positionName}, depth: ${inject.depth}, scan: ${inject.scan ?? false}) ${inject.value}`,
|
||||
enumTypes.enum, '💉');
|
||||
});
|
||||
},
|
||||
};
|
@ -1,18 +1,68 @@
|
||||
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
||||
import { SlashCommandScope } from './SlashCommandScope.js';
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {'enum' | 'command' | 'namedArgument' | 'variable' | 'qr' | 'macro' | 'number' | 'name'} EnumType
|
||||
*/
|
||||
|
||||
/**
|
||||
* Collection of the enum types that can be used with `SlashCommandEnumValue`
|
||||
*
|
||||
* Contains documentation on which color this will result to
|
||||
*/
|
||||
export const enumTypes = {
|
||||
/** 'enum' - [string] - light orange @type {EnumType} */
|
||||
enum: 'enum',
|
||||
/** 'command' - [cmd] - light yellow @type {EnumType} */
|
||||
command: 'command',
|
||||
/** 'namedArgument' - [argName] - sky blue @type {EnumType} */
|
||||
namedArgument: 'namedArgument',
|
||||
/** 'variable' - [punctuationL1] - pink @type {EnumType} */
|
||||
variable: 'variable',
|
||||
/** 'qr' - [variable] - light blue @type {EnumType} */
|
||||
qr: 'qr',
|
||||
/** 'macro' - [variableLanguage] - blue @type {EnumType} */
|
||||
macro: 'macro',
|
||||
/** 'number' - [number] - light green @type {EnumType} */
|
||||
number: 'number',
|
||||
/** 'name' - [type] - forest green @type {EnumType} */
|
||||
name: 'name',
|
||||
|
||||
/**
|
||||
* Gets the value of the enum type based on the provided index
|
||||
*
|
||||
* Can be used to get differing colors or even random colors, by providing the index of a unique set
|
||||
*
|
||||
* @param {number?} index - The index used to retrieve the enum type
|
||||
* @return {EnumType} The enum type corresponding to the index
|
||||
*/
|
||||
getBasedOnIndex(index) {
|
||||
const keys = Object.keys(this);
|
||||
return this[keys[(index ?? 0) % keys.length]];
|
||||
}
|
||||
}
|
||||
|
||||
export class SlashCommandEnumValue {
|
||||
/**@type {string}*/ value;
|
||||
/**@type {string}*/ description;
|
||||
/**@type {string}*/ type = 'enum';
|
||||
/**@type {EnumType}*/ type = 'enum';
|
||||
/**@type {string}*/ typeIcon = '◊';
|
||||
/**@type {(input:string)=>boolean}*/ matchProvider;
|
||||
/**@type {(input:string)=>string}*/ valueProvider;
|
||||
|
||||
/**
|
||||
* A constructor for creating a SlashCommandEnumValue instance.
|
||||
*
|
||||
* @param {string} value - The value
|
||||
* @param {string?} description - Optional description, displayed in a second line
|
||||
* @param {EnumType?} type - type of the enum (defining its color)
|
||||
* @param {string} typeIcon - The icon to display (Can be pulled from `enumIcons` for common ones)
|
||||
*/
|
||||
constructor(value, description = null, type = 'enum', typeIcon = '◊', matchProvider, valueProvider) {
|
||||
this.value = value;
|
||||
this.description = description;
|
||||
this.type = type;
|
||||
this.type = type ?? 'enum';
|
||||
this.typeIcon = typeIcon;
|
||||
this.matchProvider = matchProvider;
|
||||
this.valueProvider = valueProvider;
|
||||
|
@ -19,6 +19,10 @@ import { SlashCommandEnumValue } from './SlashCommandEnumValue.js';
|
||||
import { MacroAutoCompleteOption } from '../autocomplete/MacroAutoCompleteOption.js';
|
||||
import { SlashCommandBreakPoint } from './SlashCommandBreakPoint.js';
|
||||
import { SlashCommandDebugController } from './SlashCommandDebugController.js';
|
||||
import { commonEnumProviders } from './SlashCommandCommonEnumsProvider.js';
|
||||
|
||||
/** @typedef {import('./SlashCommand.js').NamedArgumentsCapture} NamedArgumentsCapture */
|
||||
/** @typedef {import('./SlashCommand.js').NamedArguments} NamedArguments */
|
||||
|
||||
/**@readonly*/
|
||||
/**@enum {Number}*/
|
||||
@ -34,7 +38,7 @@ export class SlashCommandParser {
|
||||
/**
|
||||
* @deprecated Use SlashCommandParser.addCommandObject() instead.
|
||||
* @param {string} command Command name
|
||||
* @param {(namedArguments:Object.<string,string|SlashCommandClosure>, unnamedArguments:string|SlashCommandClosure|(string|SlashCommandClosure)[])=>string|SlashCommandClosure|void|Promise<string|SlashCommandClosure|void>} callback The function to execute when the command is called
|
||||
* @param {(namedArguments:NamedArguments|NamedArgumentsCapture, unnamedArguments:string|SlashCommandClosure|(string|SlashCommandClosure)[])=>string|SlashCommandClosure|Promise<string|SlashCommandClosure>} callback callback The function to execute when the command is called
|
||||
* @param {string[]} aliases List of alternative command names
|
||||
* @param {string} helpString Help text shown in autocomplete and command browser
|
||||
*/
|
||||
@ -134,7 +138,7 @@ export class SlashCommandParser {
|
||||
description: 'The state of the parser flag to set.',
|
||||
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||
defaultValue: 'on',
|
||||
enumList: ['on', 'off'],
|
||||
enumList: commonEnumProviders.boolean('onOff')(),
|
||||
}),
|
||||
],
|
||||
splitUnnamedArgument: true,
|
||||
|
@ -2,7 +2,6 @@ import {
|
||||
characters,
|
||||
saveSettingsDebounced,
|
||||
this_chid,
|
||||
callPopup,
|
||||
menu_type,
|
||||
entitiesFilter,
|
||||
printCharactersDebounced,
|
||||
@ -10,6 +9,8 @@ import {
|
||||
eventSource,
|
||||
event_types,
|
||||
DEFAULT_PRINT_TIMEOUT,
|
||||
substituteParams,
|
||||
printCharacters,
|
||||
} from '../script.js';
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { FILTER_TYPES, FILTER_STATES, DEFAULT_FILTER_STATE, isFilterState, FilterHelper } from './filters.js';
|
||||
@ -21,11 +22,13 @@ import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
|
||||
import { isMobile } from './RossAscends-mods.js';
|
||||
import { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from './popup.js';
|
||||
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
|
||||
import { debounce_timeout } from './constants.js';
|
||||
import { INTERACTABLE_CONTROL_CLASS } from './keyboard.js';
|
||||
import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
|
||||
import { SlashCommandExecutor } from './slash-commands/SlashCommandExecutor.js';
|
||||
import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
import { renderTemplateAsync } from './templates.js';
|
||||
|
||||
export {
|
||||
TAG_FOLDER_TYPES,
|
||||
@ -62,11 +65,20 @@ function getFilterHelper(listSelector) {
|
||||
return $(listSelector).is(GROUP_FILTER_SELECTOR) ? groupCandidatesFilter : entitiesFilter;
|
||||
}
|
||||
|
||||
export const tag_filter_types = {
|
||||
/** @enum {number} */
|
||||
export const tag_filter_type = {
|
||||
character: 0,
|
||||
group_member: 1,
|
||||
};
|
||||
|
||||
/** @enum {number} */
|
||||
export const tag_import_setting = {
|
||||
ASK: 1,
|
||||
NONE: 2,
|
||||
ALL: 3,
|
||||
ONLY_EXISTING: 4,
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {{ FAV: Tag, GROUP: Tag, FOLDER: Tag, VIEW: Tag, HINT: Tag, UNFILTER: Tag }}
|
||||
* A collection of global actional tags for the filter panel
|
||||
@ -242,16 +254,23 @@ function isBogusFolder(tag) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether a user is currently in a bogus folder.
|
||||
* Retrieves all currently open bogus folders
|
||||
*
|
||||
* @return {Tag[]} An array of open bogus folders
|
||||
*/
|
||||
function getOpenBogusFolders() {
|
||||
return entitiesFilter.getFilterData(FILTER_TYPES.TAG)?.selected
|
||||
.map(tagId => tags.find(x => x.id === tagId))
|
||||
.filter(isBogusFolder) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether a user is currently in a bogus folder
|
||||
*
|
||||
* @returns {boolean} If currently viewing a folder
|
||||
*/
|
||||
function isBogusFolderOpen() {
|
||||
const anyIsFolder = entitiesFilter.getFilterData(FILTER_TYPES.TAG)?.selected
|
||||
.map(tagId => tags.find(x => x.id === tagId))
|
||||
.some(isBogusFolder);
|
||||
|
||||
return !!anyIsFolder;
|
||||
return getOpenBogusFolders().length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -283,11 +302,12 @@ function chooseBogusFolder(source, tagId, remove = false) {
|
||||
* Builds the tag block for the specified item.
|
||||
*
|
||||
* @param {Tag} tag The tag item
|
||||
* @param {*} entities The list ob sub items for this tag
|
||||
* @param {*} hidden A count of how many sub items are hidden
|
||||
* @param {any[]} entities The list ob sub items for this tag
|
||||
* @param {number} hidden A count of how many sub items are hidden
|
||||
* @param {boolean} isUseless Whether the tag is useless (should be displayed greyed out)
|
||||
* @returns The html for the tag block
|
||||
*/
|
||||
function getTagBlock(tag, entities, hidden = 0) {
|
||||
function getTagBlock(tag, entities, hidden = 0, isUseless = false) {
|
||||
let count = entities.length;
|
||||
|
||||
const tagFolder = TAG_FOLDER_TYPES[tag.folder_type];
|
||||
@ -300,6 +320,7 @@ function getTagBlock(tag, entities, hidden = 0) {
|
||||
template.find('.bogus_folder_hidden_counter').text(hidden > 0 ? `${hidden} hidden` : '');
|
||||
template.find('.bogus_folder_counter').text(`${count} ${count != 1 ? 'characters' : 'character'}`);
|
||||
template.find('.bogus_folder_icon').addClass(tagFolder.fa_icon);
|
||||
if (isUseless) template.addClass('useless');
|
||||
|
||||
// Fill inline character images
|
||||
buildAvatarList(template.find('.bogus_folder_avatars_block'), entities);
|
||||
@ -473,6 +494,27 @@ export function getTagKeyForEntityElement(element) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the key for char/group by searching based on the name or avatar. If none can be found, a toastr will be shown and null returned.
|
||||
* This function is mostly used in slash commands.
|
||||
*
|
||||
* @param {string?} [charName] The optionally provided char name
|
||||
* @param {object} [options] - Optional arguments
|
||||
* @param {boolean} [options.suppressLogging=false] - Whether to suppress the toastr warning
|
||||
* @returns {string?} - The char/group key, or null if none found
|
||||
*/
|
||||
export function searchCharByName(charName, { suppressLogging = false } = {}) {
|
||||
const entity = charName
|
||||
? (characters.find(x => x.name === charName) || groups.find(x => x.name == charName))
|
||||
: (selected_group ? groups.find(x => x.id == selected_group) : characters[this_chid]);
|
||||
const key = getTagKeyForEntity(entity);
|
||||
if (!key) {
|
||||
if (!suppressLogging) toastr.warning(`Character ${charName} not found.`);
|
||||
return null;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds one or more tags to a given entity
|
||||
*
|
||||
@ -660,15 +702,6 @@ function getExistingTags(newTags) {
|
||||
return existingTags;
|
||||
}
|
||||
|
||||
const tagImportSettings = {
|
||||
ALWAYS_IMPORT_ALL: 1,
|
||||
ONLY_IMPORT_EXISTING: 2,
|
||||
IMPORT_NONE: 3,
|
||||
ASK: 4,
|
||||
};
|
||||
|
||||
let globalTagImportSetting = tagImportSettings.ASK; // Default setting
|
||||
|
||||
const IMPORT_EXLCUDED_TAGS = ['ROOT', 'TAVERN'];
|
||||
const ANTI_TROLL_MAX_TAGS = 15;
|
||||
|
||||
@ -676,11 +709,13 @@ const ANTI_TROLL_MAX_TAGS = 15;
|
||||
* Imports tags for a given character
|
||||
*
|
||||
* @param {Character} character - The character
|
||||
* @param {object} [options] - Options
|
||||
* @param {boolean} [options.forceShow=false] - Whether to force showing the import dialog
|
||||
* @returns {Promise<boolean>} Boolean indicating whether any tag was imported
|
||||
*/
|
||||
async function importTags(character) {
|
||||
async function importTags(character, { forceShow = false } = {}) {
|
||||
// Gather the tags to import based on the selected setting
|
||||
const tagNamesToImport = await handleTagImport(character);
|
||||
const tagNamesToImport = await handleTagImport(character, { forceShow });
|
||||
if (!tagNamesToImport?.length) {
|
||||
toastr.info('No tags imported', 'Importing Tags');
|
||||
return;
|
||||
@ -698,9 +733,11 @@ async function importTags(character) {
|
||||
* Handles the import of tags for a given character and returns the resulting list of tags to add
|
||||
*
|
||||
* @param {Character} character - The character
|
||||
* @param {object} [options] - Options
|
||||
* @param {boolean} [options.forceShow=false] - Whether to force showing the import dialog
|
||||
* @returns {Promise<string[]>} Array of strings representing the tags to import
|
||||
*/
|
||||
async function handleTagImport(character) {
|
||||
async function handleTagImport(character, { forceShow = false } = {}) {
|
||||
/** @type {string[]} */
|
||||
const importTags = character.tags.map(t => t.trim()).filter(t => t)
|
||||
.filter(t => !IMPORT_EXLCUDED_TAGS.includes(t))
|
||||
@ -708,17 +745,22 @@ async function handleTagImport(character) {
|
||||
const existingTags = getExistingTags(importTags);
|
||||
const newTags = importTags.filter(t => !existingTags.some(existingTag => existingTag.name.toLowerCase() === t.toLowerCase()))
|
||||
.map(newTag);
|
||||
const folderTags = getOpenBogusFolders();
|
||||
|
||||
switch (globalTagImportSetting) {
|
||||
case tagImportSettings.ALWAYS_IMPORT_ALL:
|
||||
return existingTags.concat(newTags).map(t => t.name);
|
||||
case tagImportSettings.ONLY_IMPORT_EXISTING:
|
||||
return existingTags.map(t => t.name);
|
||||
case tagImportSettings.ASK:
|
||||
return await showTagImportPopup(character, existingTags, newTags);
|
||||
case tagImportSettings.IMPORT_NONE:
|
||||
default:
|
||||
// Choose the setting for this dialog. If from settings, verify the setting really exists, otherwise take "ASK".
|
||||
const setting = forceShow ? tag_import_setting.ASK
|
||||
: Object.values(tag_import_setting).find(setting => setting === power_user.tag_import_setting) ?? tag_import_setting.ASK;
|
||||
|
||||
switch (setting) {
|
||||
case tag_import_setting.ALL:
|
||||
return [...existingTags, ...newTags, ...folderTags].map(t => t.name);
|
||||
case tag_import_setting.ONLY_EXISTING:
|
||||
return [...existingTags, ...folderTags].map(t => t.name);
|
||||
case tag_import_setting.ASK:
|
||||
return await showTagImportPopup(character, existingTags, newTags, folderTags);
|
||||
case tag_import_setting.NONE:
|
||||
return [];
|
||||
default: throw new Error(`Invalid tag import setting: ${setting}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -728,63 +770,55 @@ async function handleTagImport(character) {
|
||||
* @param {Character} character - The character
|
||||
* @param {Tag[]} existingTags - List of existing tags
|
||||
* @param {Tag[]} newTags - List of new tags
|
||||
* @param {Tag[]} folderTags - List of tags in the current folder
|
||||
* @returns {Promise<string[]>} Array of strings representing the tags to import
|
||||
*/
|
||||
async function showTagImportPopup(character, existingTags, newTags) {
|
||||
async function showTagImportPopup(character, existingTags, newTags, folderTags) {
|
||||
/** @type {{[key: string]: import('./popup.js').CustomPopupButton}} */
|
||||
const importButtons = {
|
||||
EXISTING: { result: 2, text: 'Import Existing' },
|
||||
NONE: { result: 2, text: 'Import None' },
|
||||
ALL: { result: 3, text: 'Import All' },
|
||||
NONE: { result: 4, text: 'Import None' },
|
||||
EXISTING: { result: 4, text: 'Import Existing' },
|
||||
};
|
||||
const buttonSettingsMap = {
|
||||
[POPUP_RESULT.AFFIRMATIVE]: tag_import_setting.ASK,
|
||||
[importButtons.NONE.result]: tag_import_setting.NONE,
|
||||
[importButtons.ALL.result]: tag_import_setting.ALL,
|
||||
[importButtons.EXISTING.result]: tag_import_setting.ONLY_EXISTING,
|
||||
};
|
||||
|
||||
const customButtonsCaptions = Object.values(importButtons).map(button => `"${button.text}"`);
|
||||
const customButtonsString = customButtonsCaptions.slice(0, -1).join(', ') + ' or ' + customButtonsCaptions.slice(-1);
|
||||
|
||||
const popupContent = $(`
|
||||
<h3>Import Tags For ${character.name}</h3>
|
||||
<div class="import_avatar_placeholder"></div>
|
||||
<div class="import_tags_content justifyLeft">
|
||||
<small>
|
||||
Click remove on any tag to remove it from this import.<br />
|
||||
Select one of the import options to finish importing the tags.
|
||||
</small>
|
||||
|
||||
<h4 class="m-t-1">Existing Tags</h4>
|
||||
<div id="import_existing_tags_list" class="tags"></div>
|
||||
|
||||
<h4 class="m-t-1">New Tags</h4>
|
||||
<div id="import_new_tags_list" class="tags"></div>
|
||||
|
||||
<small>
|
||||
<label class="checkbox flex-container alignitemscenter flexNoGap m-t-3" for="import_remember_option">
|
||||
<input type="checkbox" id="import_remember_option" name="import_remember_option" />
|
||||
<span data-i18n="Remember my choice">
|
||||
Remember my choice
|
||||
<div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]Remember the chosen import option\nIf ${customButtonsString} is selected, this dialog will not show up anymore.\nTo change this, go to the settings and modify "Tag Import Option".\n\nIf the "Import" option is chosen, the global setting will stay on "Ask"."
|
||||
title="Remember the chosen import option\nIf ${customButtonsString} is selected, this dialog will not show up anymore.\nTo change this, go to the settings and modify "Tag Import Option".\n\nIf the "Import" option is chosen, the global setting will stay on "Ask".">
|
||||
</div>
|
||||
</span>
|
||||
</label>
|
||||
</small>
|
||||
</div>`);
|
||||
const popupContent = $(await renderTemplateAsync('charTagImport', { charName: character.name }));
|
||||
|
||||
// Print tags after popup is shown, so that events can be added
|
||||
printTagList(popupContent.find('#import_existing_tags_list'), { tags: existingTags, tagOptions: { removable: true, removeAction: tag => removeFromArray(existingTags, tag) } });
|
||||
printTagList(popupContent.find('#import_new_tags_list'), { tags: newTags, tagOptions: { removable: true, removeAction: tag => removeFromArray(newTags, tag) } });
|
||||
printTagList(popupContent.find('#import_folder_tags_list'), { tags: folderTags, tagOptions: { removable: true, removeAction: tag => removeFromArray(folderTags, tag) } });
|
||||
|
||||
const result = await callGenericPopup(popupContent, POPUP_TYPE.TEXT, null, { wider: true, okButton: 'Import', cancelButton: true, customButtons: Object.values(importButtons) });
|
||||
if (folderTags.length === 0) popupContent.find('#folder_tags_block').hide();
|
||||
|
||||
function onCloseRemember(/** @type {Popup} */ popup) {
|
||||
const rememberCheckbox = document.getElementById('import_remember_option');
|
||||
if (rememberCheckbox instanceof HTMLInputElement && rememberCheckbox.checked) {
|
||||
const setting = buttonSettingsMap[popup.result];
|
||||
if (!setting) return;
|
||||
power_user.tag_import_setting = setting;
|
||||
$('#tag_import_setting').val(power_user.tag_import_setting);
|
||||
saveSettingsDebounced();
|
||||
console.log('Remembered tag import setting:', Object.entries(tag_import_setting).find(x => x[1] === setting)[0], setting);
|
||||
}
|
||||
}
|
||||
|
||||
const result = await callGenericPopup(popupContent, POPUP_TYPE.TEXT, null, { wider: true, okButton: 'Import', cancelButton: true, customButtons: Object.values(importButtons), onClose: onCloseRemember });
|
||||
if (!result) {
|
||||
return [];
|
||||
}
|
||||
|
||||
switch (result) {
|
||||
case 1:
|
||||
case true:
|
||||
case importButtons.ALL.result: // Default 'Import' option where it imports all selected
|
||||
return existingTags.concat(newTags).map(t => t.name);
|
||||
case POPUP_RESULT.AFFIRMATIVE: // Default 'Import' option where it imports all selected
|
||||
case importButtons.ALL.result:
|
||||
return [...existingTags, ...newTags, ...folderTags].map(t => t.name);
|
||||
case importButtons.EXISTING.result:
|
||||
return existingTags.map(t => t.name);
|
||||
return [...existingTags, ...folderTags].map(t => t.name);
|
||||
case importButtons.NONE.result:
|
||||
default:
|
||||
return [];
|
||||
@ -1113,8 +1147,8 @@ function runTagFilters(listElement) {
|
||||
filterHelper.setFilterData(FILTER_TYPES.TAG, { excluded: excludedTagIds, selected: tagIds });
|
||||
}
|
||||
|
||||
function printTagFilters(type = tag_filter_types.character) {
|
||||
const FILTER_SELECTOR = type === tag_filter_types.character ? CHARACTER_FILTER_SELECTOR : GROUP_FILTER_SELECTOR;
|
||||
function printTagFilters(type = tag_filter_type.character) {
|
||||
const FILTER_SELECTOR = type === tag_filter_type.character ? CHARACTER_FILTER_SELECTOR : GROUP_FILTER_SELECTOR;
|
||||
$(FILTER_SELECTOR).empty();
|
||||
|
||||
// Print all action tags. (Rework 'Folder' button to some kind of onboarding if no folders are enabled yet)
|
||||
@ -1133,9 +1167,7 @@ function printTagFilters(type = tag_filter_types.character) {
|
||||
const bogusDrilldown = $(FILTER_SELECTOR).siblings('.rm_tag_bogus_drilldown');
|
||||
bogusDrilldown.empty();
|
||||
if (power_user.bogus_folders && bogusDrilldown.length > 0) {
|
||||
const filterData = structuredClone(entitiesFilter.getFilterData(FILTER_TYPES.TAG));
|
||||
const navigatedTags = filterData.selected.map(x => tags.find(t => t.id == x)).filter(x => isBogusFolder(x));
|
||||
|
||||
const navigatedTags = getOpenBogusFolders();
|
||||
printTagList(bogusDrilldown, { tags: navigatedTags, tagOptions: { removable: true } });
|
||||
}
|
||||
|
||||
@ -1760,22 +1792,6 @@ function printViewTagList(tagContainer, empty = true) {
|
||||
}
|
||||
|
||||
function registerTagsSlashCommands() {
|
||||
/**
|
||||
* Gets the key for char/group for a slash command. If none can be found, a toastr will be shown and null returned.
|
||||
* @param {string?} [charName] The optionally provided char name
|
||||
* @returns {string?} - The char/group key, or null if none found
|
||||
*/
|
||||
function paraGetCharKey(charName) {
|
||||
const entity = charName
|
||||
? (characters.find(x => x.name === charName) || groups.find(x => x.name == charName))
|
||||
: (selected_group ? groups.find(x => x.id == selected_group) : characters[this_chid]);
|
||||
const key = getTagKeyForEntity(entity);
|
||||
if (!key) {
|
||||
toastr.warning(`Character ${charName} not found.`);
|
||||
return null;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
/**
|
||||
* Gets a tag by its name. Optionally can create the tag if it does not exist.
|
||||
* @param {string} tagName - The name of the tag
|
||||
@ -1804,11 +1820,12 @@ function registerTagsSlashCommands() {
|
||||
returns: 'true/false - Whether the tag was added or was assigned already',
|
||||
/** @param {{name: string}} namedArgs @param {string} tagName @returns {string} */
|
||||
callback: ({ name }, tagName) => {
|
||||
const key = paraGetCharKey(name);
|
||||
const key = searchCharByName(name);
|
||||
if (!key) return 'false';
|
||||
const tag = paraGetTag(tagName, { allowCreate: true });
|
||||
if (!tag) return 'false';
|
||||
const result = addTagsToEntity(tag, key);
|
||||
printCharacters();
|
||||
return String(result);
|
||||
},
|
||||
namedArgumentList: [
|
||||
@ -1816,22 +1833,14 @@ function registerTagsSlashCommands() {
|
||||
description: 'Character name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
defaultValue: '{{char}}',
|
||||
enumProvider: ()=>[
|
||||
...characters.map(it=>new SlashCommandEnumValue(it.name, null, 'qr', 'C')),
|
||||
...groups.map(it=>new SlashCommandEnumValue(it.name, null, 'variable', 'G')),
|
||||
],
|
||||
enumProvider: commonEnumProviders.characters(),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({ description: 'tag name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: (executor)=>{
|
||||
const key = paraGetCharKey(/**@type {string}*/(executor.namedArgumentList.find(it=>it.name == 'name')?.value));
|
||||
if (!key) return tags.map(it=>new SlashCommandEnumValue(it.name, it.title));
|
||||
const assigned = getTagsList(key);
|
||||
return tags.filter(it=>!assigned.includes(it)).map(it=>new SlashCommandEnumValue(it.name, it.title));
|
||||
},
|
||||
enumProvider: commonEnumProviders.tagsForChar('not-existing'),
|
||||
forceEnum: false,
|
||||
}),
|
||||
],
|
||||
@ -1856,11 +1865,12 @@ function registerTagsSlashCommands() {
|
||||
returns: 'true/false - Whether the tag was removed or wasn\'t assigned already',
|
||||
/** @param {{name: string}} namedArgs @param {string} tagName @returns {string} */
|
||||
callback: ({ name }, tagName) => {
|
||||
const key = paraGetCharKey(name);
|
||||
const key = searchCharByName(name);
|
||||
if (!key) return 'false';
|
||||
const tag = paraGetTag(tagName);
|
||||
if (!tag) return 'false';
|
||||
const result = removeTagFromEntity(tag, key);
|
||||
printCharacters();
|
||||
return String(result);
|
||||
},
|
||||
namedArgumentList: [
|
||||
@ -1868,10 +1878,7 @@ function registerTagsSlashCommands() {
|
||||
description: 'Character name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
defaultValue: '{{char}}',
|
||||
enumProvider: ()=>[
|
||||
...characters.map(it=>new SlashCommandEnumValue(it.name, null, 'qr', 'C')),
|
||||
...groups.map(it=>new SlashCommandEnumValue(it.name, null, 'variable', 'G')),
|
||||
],
|
||||
enumProvider: commonEnumProviders.characters(),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
@ -1879,11 +1886,7 @@ function registerTagsSlashCommands() {
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
/**@param {SlashCommandExecutor} executor */
|
||||
enumProvider: (executor)=>{
|
||||
const key = paraGetCharKey(/**@type {string}*/(executor.namedArgumentList.find(it=>it.name == 'name')?.value));
|
||||
if (!key) return tags.map(it=>new SlashCommandEnumValue(it.name, it.title));
|
||||
return getTagsList(key).map(it=>new SlashCommandEnumValue(it.name, it.title));
|
||||
},
|
||||
enumProvider: commonEnumProviders.tagsForChar('existing'),
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
@ -1906,17 +1909,29 @@ function registerTagsSlashCommands() {
|
||||
returns: 'true/false - Whether the given tag name is assigned to the character',
|
||||
/** @param {{name: string}} namedArgs @param {string} tagName @returns {string} */
|
||||
callback: ({ name }, tagName) => {
|
||||
const key = paraGetCharKey(name);
|
||||
const key = searchCharByName(name);
|
||||
if (!key) return 'false';
|
||||
const tag = paraGetTag(tagName);
|
||||
if (!tag) return 'false';
|
||||
return String(tag_map[key].includes(tag.id));
|
||||
},
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument('name', 'Character name', [ARGUMENT_TYPE.STRING], false, false, '{{char}}'),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'name',
|
||||
description: 'Character name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
defaultValue: '{{char}}',
|
||||
enumProvider: commonEnumProviders.characters(),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument('tag name', [ARGUMENT_TYPE.STRING], true),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'tag name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
/**@param {SlashCommandExecutor} executor */
|
||||
enumProvider: commonEnumProviders.tagsForChar('all'),
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
@ -1938,13 +1953,19 @@ function registerTagsSlashCommands() {
|
||||
returns: 'Comma-separated list of all assigned tags',
|
||||
/** @param {{name: string}} namedArgs @returns {string} */
|
||||
callback: ({ name }) => {
|
||||
const key = paraGetCharKey(name);
|
||||
const key = searchCharByName(name);
|
||||
if (!key) return '';
|
||||
const tags = getTagsList(key);
|
||||
return tags.map(x => x.name).join(', ');
|
||||
},
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument('name', 'Character name', [ARGUMENT_TYPE.STRING], false, false, '{{char}}'),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'name',
|
||||
description: 'Character name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
defaultValue: '{{char}}',
|
||||
enumProvider: commonEnumProviders.characters(),
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
|
35
public/scripts/templates/charTagImport.html
Normal file
35
public/scripts/templates/charTagImport.html
Normal file
@ -0,0 +1,35 @@
|
||||
<h3>Import Tags For {{charName}}</h3>
|
||||
<div class="import_avatar_placeholder"></div>
|
||||
<div class="import_tags_content justifyLeft">
|
||||
<small data-i18n="Click remove on any tag to remove it from this import.<br />Select one of the import options to finish importing the tags.">
|
||||
Click remove on any tag to remove it from this import.<br />
|
||||
Select one of the import options to finish importing the tags.
|
||||
</small>
|
||||
|
||||
<h4 class="m-t-1" data-i18n="Existing Tags">Existing Tags</h4>
|
||||
<div id="import_existing_tags_list" class="tags" style="min-height: 20px;"></div>
|
||||
|
||||
<h4 class="m-t-1" data-i18n="New Tags">New Tags</h4>
|
||||
<div id="import_new_tags_list" class="tags" style="min-height: 20px;"></div>
|
||||
|
||||
<div id="folder_tags_block" class="m-t-1">
|
||||
<h4 data-i18n="Folder Tags">Folder Tags</h4>
|
||||
<small data-i18n="The following tags will be auto-imported based on the currently selected folders">
|
||||
The following tags will be auto-imported based on the currently selected folders
|
||||
</small>
|
||||
<div id="import_folder_tags_list" class="tags" style="margin-top: 5px;"></div>
|
||||
</div>
|
||||
|
||||
<small>
|
||||
<label class="checkbox flex-container alignitemscenter flexNoGap m-t-3" for="import_remember_option">
|
||||
<input type="checkbox" id="import_remember_option" name="import_remember_option" />
|
||||
<span data-i18n="Remember my choice">
|
||||
Remember my choice
|
||||
<div class="fa-solid fa-circle-info opacity50p"
|
||||
data-i18n="[title]Remember the chosen import option
If anything besides 'Cancel' is selected, this dialog will not show up anymore.
To change this, go to the settings and modify "Tag Import Option".

If the "Import" option is chosen, the global setting will stay on "Ask"."
|
||||
title="Remember the chosen import option
If anything besides 'Cancel' is selected, this dialog will not show up anymore.
To change this, go to the settings and modify "Tag Import Option".

If the "Import" option is chosen, the global setting will stay on "Ask".">
|
||||
</div>
|
||||
</span>
|
||||
</label>
|
||||
</small>
|
||||
</div>
|
@ -21,9 +21,11 @@
|
||||
<li><tt>{{char_version}}</tt> – <span data-i18n="help_macros_17">the Character's version number</span></li>
|
||||
<li><tt>{{group}}</tt> – <span data-i18n="help_macros_18">a comma-separated list of group member names or the character name in solo chats. Alias: {{charIfNotGroup}}</span></li>
|
||||
<li><tt>{{model}}</tt> – <span data-i18n="help_macros_19">a text generation model name for the currently selected API. </span><b data-i18n="Can be inaccurate!">Can be inaccurate!</b></li>
|
||||
<li><tt>{{lastMessage}}</tt> - <span data-i18n="help_macros_20">the text of the latest chat message.</span></li>
|
||||
<li><tt>{{lastMessage}}</tt> – <span data-i18n="help_macros_20">the text of the latest chat message.</span></li>
|
||||
<li><tt>{{lastUserMessage}}</tt> – <span data-i18n="help_macros_lastUser">the text of the latest user chat message.</span></li>
|
||||
<li><tt>{{lastCharMessage}}</tt> – <span data-i18n="help_macros_lastChar">the text of the latest character chat message.</span></li>
|
||||
<li><tt>{{lastMessageId}}</tt> – <span data-i18n="help_macros_21">index # of the latest chat message. Useful for slash command batching.</span></li>
|
||||
<li><tt>{{firstIncludedMessageId}}</tt> - <span data-i18n="help_macros_22">the ID of the first message included in the context. Requires generation to be ran at least once in the current session.</span></li>
|
||||
<li><tt>{{firstIncludedMessageId}}</tt> – <span data-i18n="help_macros_22">the ID of the first message included in the context. Requires generation to be ran at least once in the current session.</span></li>
|
||||
<li><tt>{{currentSwipeId}}</tt> – <span data-i18n="help_macros_23">the 1-based ID of the current swipe in the last chat message. Empty string if the last message is user or prompt-hidden.</span></li>
|
||||
<li><tt>{{lastSwipeId}}</tt> – <span data-i18n="help_macros_24">the number of swipes in the last chat message. Empty string if the last message is user or prompt-hidden.</span></li>
|
||||
<li><tt>{{// (note)}}</tt> – <span data-i18n="help_macros_25">you can leave a note here, and the macro will be replaced with blank content. Not visible for the AI.</span></li>
|
||||
|
@ -39,6 +39,8 @@ const OPENROUTER_PROVIDERS = [
|
||||
'Novita',
|
||||
'Lynn',
|
||||
'Lynn 2',
|
||||
'DeepSeek',
|
||||
'Infermatic',
|
||||
];
|
||||
|
||||
export async function loadOllamaModels(data) {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -13,6 +13,10 @@ import { getRegexedString, regex_placement } from './extensions/regex/engine.js'
|
||||
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
|
||||
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
|
||||
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
import { SlashCommandExecutor } from './slash-commands/SlashCommandExecutor.js';
|
||||
import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
|
||||
|
||||
export {
|
||||
world_info,
|
||||
@ -698,20 +702,55 @@ function registerWorldInfoSlashCommands() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/** A collection of local enum providers for this context of world info */
|
||||
const localEnumProviders = {
|
||||
/** All possible fields that can be set in a WI entry */
|
||||
wiEntryFields: () => Object.entries(newEntryDefinition).map(([key, value]) =>
|
||||
new SlashCommandEnumValue(key, `[${value.type}] default: ${(typeof value.default === 'string' ? `'${value.default}'` : value.default)}`,
|
||||
enumTypes.enum, enumIcons.getDataTypeIcon(value.type))),
|
||||
|
||||
/** All existing UIDs based on the file argument as world name */
|
||||
wiUids: (/** @type {SlashCommandExecutor} */ executor) => {
|
||||
const file = executor.namedArgumentList.find(it => it.name == 'file')?.value;
|
||||
if (file instanceof SlashCommandClosure) throw new Error('Argument \'file\' does not support closures');
|
||||
// Try find world from cache
|
||||
const world = worldInfoCache[file];
|
||||
if (!world) return [];
|
||||
return Object.entries(world.entries).map(([uid, data]) =>
|
||||
new SlashCommandEnumValue(uid, `${data.comment ? `${data.comment}: ` : ''}${data.key.join(', ')}${data.keysecondary?.length ? ` [${Object.entries(world_info_logic).find(([_, value]) => value == data.selectiveLogic)[0]}] ${data.keysecondary.join(', ')}` : ''} [${getWiPositionString(data)}]`,
|
||||
enumTypes.enum, enumIcons.getWiStatusIcon(data)));
|
||||
},
|
||||
};
|
||||
|
||||
function getWiPositionString(entry) {
|
||||
switch (entry.position) {
|
||||
case world_info_position.before: return '↑Char';
|
||||
case world_info_position.after: return '↓Char';
|
||||
case world_info_position.EMTop: return '↑EM';
|
||||
case world_info_position.EMBottom: return '↓EM';
|
||||
case world_info_position.ANTop: return '↑AT';
|
||||
case world_info_position.ANBottom: return '↓AT';
|
||||
case world_info_position.atDepth: return `@D${enumIcons.getRoleIcon(entry.role)}`;
|
||||
default: return '<Unknown>';
|
||||
}
|
||||
}
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'world',
|
||||
callback: onWorldInfoChange,
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'state', 'set world state', [ARGUMENT_TYPE.STRING], false, false, null, ['off', 'toggle'],
|
||||
'state', 'set world state', [ARGUMENT_TYPE.STRING], false, false, null, commonEnumProviders.boolean('onOffToggle')(),
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'silent', 'suppress toast messages', [ARGUMENT_TYPE.BOOLEAN], false,
|
||||
),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'name', [ARGUMENT_TYPE.STRING], false,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'world name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: commonEnumProviders.worlds,
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
@ -726,17 +765,26 @@ function registerWorldInfoSlashCommands() {
|
||||
helpString: 'Get a name of the chat-bound lorebook or create a new one if was unbound, and pass it down the pipe.',
|
||||
aliases: ['getchatlore', 'getchatwi'],
|
||||
}));
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'findentry',
|
||||
aliases: ['findlore', 'findwi'],
|
||||
returns: 'UID',
|
||||
callback: findBookEntryCallback,
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'file', 'bookName', ARGUMENT_TYPE.STRING, true,
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'field', 'field value for fuzzy match (default: key)', ARGUMENT_TYPE.STRING, false, false, 'key',
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'file',
|
||||
description: 'book name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: commonEnumProviders.worlds,
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'field',
|
||||
description: 'field value for fuzzy match (default: key)',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
defaultValue: 'key',
|
||||
enumList: localEnumProviders.wiEntryFields(),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
@ -762,17 +810,28 @@ function registerWorldInfoSlashCommands() {
|
||||
callback: getEntryFieldCallback,
|
||||
returns: 'field value',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'file', 'bookName', ARGUMENT_TYPE.STRING, true,
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'field', 'field to retrieve (default: content)', ARGUMENT_TYPE.STRING, false, false, 'content',
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'file',
|
||||
description: 'book name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: commonEnumProviders.worlds,
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'field',
|
||||
description: 'field to retrieve (default: content)',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
defaultValue: 'content',
|
||||
enumList: localEnumProviders.wiEntryFields(),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'UID', ARGUMENT_TYPE.STRING, true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'record UID',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.wiUids,
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
@ -793,9 +852,13 @@ function registerWorldInfoSlashCommands() {
|
||||
aliases: ['createlore', 'createwi'],
|
||||
returns: 'UID of the new record',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'file', 'book name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'file',
|
||||
description: 'book name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: commonEnumProviders.worlds,
|
||||
}),
|
||||
new SlashCommandNamedArgument(
|
||||
'key', 'record key', [ARGUMENT_TYPE.STRING], false,
|
||||
),
|
||||
@ -823,15 +886,27 @@ function registerWorldInfoSlashCommands() {
|
||||
callback: setEntryFieldCallback,
|
||||
aliases: ['setlorefield', 'setwifield'],
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'file', 'book name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'uid', 'record UID', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'field', 'field name', [ARGUMENT_TYPE.STRING], true, false, 'content',
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'file',
|
||||
description: 'book name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: commonEnumProviders.worlds,
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'uid',
|
||||
description: 'record UID',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.wiUids,
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'field',
|
||||
description: 'field name (default: content)',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
defaultValue: 'content',
|
||||
enumList: localEnumProviders.wiEntryFields(),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
@ -2450,36 +2525,47 @@ function deleteWorldInfoEntry(data, uid) {
|
||||
delete data.entries[uid];
|
||||
}
|
||||
|
||||
const newEntryTemplate = {
|
||||
key: [],
|
||||
keysecondary: [],
|
||||
comment: '',
|
||||
content: '',
|
||||
constant: false,
|
||||
vectorized: false,
|
||||
selective: true,
|
||||
selectiveLogic: world_info_logic.AND_ANY,
|
||||
addMemo: false,
|
||||
order: 100,
|
||||
position: 0,
|
||||
disable: false,
|
||||
excludeRecursion: false,
|
||||
preventRecursion: false,
|
||||
delayUntilRecursion: false,
|
||||
probability: 100,
|
||||
useProbability: true,
|
||||
depth: DEFAULT_DEPTH,
|
||||
group: '',
|
||||
groupOverride: false,
|
||||
groupWeight: DEFAULT_WEIGHT,
|
||||
scanDepth: null,
|
||||
caseSensitive: null,
|
||||
matchWholeWords: null,
|
||||
useGroupScoring: null,
|
||||
automationId: '',
|
||||
role: 0,
|
||||
/**
|
||||
* Definitions of types for new WI entries
|
||||
*
|
||||
* Use `newEntryTemplate` if you just need the template that contains default values
|
||||
*
|
||||
* @type {{[key: string]: { default: any, type: string }}}
|
||||
*/
|
||||
const newEntryDefinition = {
|
||||
key: { default: [], type: 'array' },
|
||||
keysecondary: { default: [], type: 'array' },
|
||||
comment: { default: '', type: 'string' },
|
||||
content: { default: '', type: 'string' },
|
||||
constant: { default: false, type: 'boolean' },
|
||||
vectorized: { default: false, type: 'boolean' },
|
||||
selective: { default: true, type: 'boolean' },
|
||||
selectiveLogic: { default: world_info_logic.AND_ANY, type: 'enum' },
|
||||
addMemo: { default: false, type: 'boolean' },
|
||||
order: { default: 100, type: 'number' },
|
||||
position: { default: 0, type: 'number' },
|
||||
disable: { default: false, type: 'boolean' },
|
||||
excludeRecursion: { default: false, type: 'boolean' },
|
||||
preventRecursion: { default: false, type: 'boolean' },
|
||||
delayUntilRecursion: { default: false, type: 'boolean' },
|
||||
probability: { default: 100, type: 'number' },
|
||||
useProbability: { default: true, type: 'boolean' },
|
||||
depth: { default: DEFAULT_DEPTH, type: 'number' },
|
||||
group: { default: '', type: 'string' },
|
||||
groupOverride: { default: false, type: 'boolean' },
|
||||
groupWeight: { default: DEFAULT_WEIGHT, type: 'number' },
|
||||
scanDepth: { default: null, type: 'number?' },
|
||||
caseSensitive: { default: null, type: 'boolean?' },
|
||||
matchWholeWords: { default: null, type: 'boolean?' },
|
||||
useGroupScoring: { default: null, type: 'boolean?' },
|
||||
automationId: { default: '', type: 'string' },
|
||||
role: { default: 0, type: 'enum' },
|
||||
};
|
||||
|
||||
const newEntryTemplate = Object.fromEntries(
|
||||
Object.entries(newEntryDefinition).map(([key, value]) => [key, value.default]),
|
||||
);
|
||||
|
||||
function createWorldInfoEntry(_name, data) {
|
||||
const newUid = getFreeWorldEntryUid(data);
|
||||
|
||||
@ -3504,6 +3590,7 @@ function onWorldInfoChange(args, text) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'on':
|
||||
default: {
|
||||
selected_world_info.push(name);
|
||||
wiElement.prop('selected', true);
|
||||
|
124
public/style.css
124
public/style.css
@ -366,11 +366,6 @@ input[type='checkbox']:focus-visible {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.img_enlarged_container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.img_enlarged_container pre code,
|
||||
.mes_text pre code {
|
||||
position: relative;
|
||||
display: block;
|
||||
@ -1516,6 +1511,14 @@ body[data-stscript-style] .autoComplete [data-option-type] {
|
||||
&[data-option-type="macro"] .type {
|
||||
color: var(--ac-color-variableLanguage);
|
||||
}
|
||||
|
||||
&[data-option-type="number"] .type {
|
||||
color: var(--ac-color-number);
|
||||
}
|
||||
|
||||
&[data-option-type="name"] .type {
|
||||
color: var(--ac-color-type);
|
||||
}
|
||||
}
|
||||
|
||||
body[data-stscript-style] .hljs.language-stscript {
|
||||
@ -1625,6 +1628,11 @@ body[data-stscript-style] .hljs.language-stscript {
|
||||
gap: 0.5em;
|
||||
display: contents;
|
||||
|
||||
>.stopgap {
|
||||
opacity: 0.75;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@container (max-width: 80em) {
|
||||
.specs {
|
||||
grid-column: 2 / 4;
|
||||
@ -1636,6 +1644,10 @@ body[data-stscript-style] .hljs.language-stscript {
|
||||
opacity: 0.75;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
>.stopgap {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
&.blank {
|
||||
@ -2363,6 +2375,10 @@ input[type="file"] {
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
#rm_print_characters_block .entity_block.useless {
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
#rm_ch_create_block {
|
||||
display: none;
|
||||
overflow-y: auto;
|
||||
@ -3135,6 +3151,16 @@ grammarly-extension {
|
||||
min-width: 750px;
|
||||
}
|
||||
|
||||
.transparent_dialogue_popup {
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.transparent_dialogue_popup:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#dialogue_popup .horizontal_scrolling_dialogue_popup {
|
||||
overflow-x: unset !important;
|
||||
}
|
||||
@ -4448,38 +4474,106 @@ a {
|
||||
|
||||
.mes_img_controls {
|
||||
position: absolute;
|
||||
top: 0.5em;
|
||||
top: 0.1em;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
display: none;
|
||||
display: flex;
|
||||
opacity: 0;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.mes_img_controls .right_menu_button {
|
||||
padding: 0;
|
||||
filter: brightness(80%);
|
||||
padding: 1px;
|
||||
height: 1.25em;
|
||||
width: 1.25em;
|
||||
}
|
||||
|
||||
.mes_img_controls .right_menu_button::before {
|
||||
/* Fix weird alignment with this font-awesome icons on focus */
|
||||
position: relative;
|
||||
top: 0.6125em;
|
||||
}
|
||||
|
||||
.mes_img_controls .right_menu_button:hover {
|
||||
filter: brightness(150%);
|
||||
}
|
||||
|
||||
.mes_img_container:hover .mes_img_controls {
|
||||
display: flex;
|
||||
.mes_img_container:hover .mes_img_controls,
|
||||
.mes_img_container:focus-within .mes_img_controls {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.mes .mes_img_container.img_extra {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.img_enlarged_holder {
|
||||
/* Scaling via flex-grow and object-fit only works if we have some kind of base-height set */
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.img_enlarged_holder:has(.zoomed) {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.img_enlarged {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 2px;
|
||||
border: 1px solid transparent;
|
||||
outline: 1px solid var(--SmartThemeBorderColor);
|
||||
object-fit: contain;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: zoom-in
|
||||
}
|
||||
|
||||
.img_enlarged.zoomed {
|
||||
object-fit: cover;
|
||||
width: auto;
|
||||
height: auto;
|
||||
cursor: zoom-out;
|
||||
}
|
||||
|
||||
.img_enlarged_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 10px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.img_enlarged_holder::-webkit-scrollbar-corner {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.img_enlarged_container pre code {
|
||||
position: relative;
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.popup:has(.img_enlarged.zoomed).large_dialogue_popup {
|
||||
height: 100vh !important;
|
||||
height: 100svh !important;
|
||||
max-height: 100vh !important;
|
||||
max-height: 100svh !important;
|
||||
max-width: 100vw !important;
|
||||
max-width: 100svw !important;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.popup:has(.img_enlarged.zoomed).large_dialogue_popup .popup-content {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.popup:has(.img_enlarged.zoomed).large_dialogue_popup .img_enlarged_container pre {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.popup:has(.img_enlarged.zoomed).large_dialogue_popup .popup-button-close {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.cropper-container {
|
||||
|
@ -191,7 +191,7 @@ router.post('/delete', jsonParser, async (request, response) => {
|
||||
return response.status(400).send('Bad Request: extensionName is required in the request body.');
|
||||
}
|
||||
|
||||
// Sanatize the extension name to prevent directory traversal
|
||||
// Sanitize the extension name to prevent directory traversal
|
||||
const extensionName = sanitize(request.body.extensionName);
|
||||
|
||||
try {
|
||||
@ -201,7 +201,7 @@ router.post('/delete', jsonParser, async (request, response) => {
|
||||
return response.status(404).send(`Directory does not exist at ${extensionPath}`);
|
||||
}
|
||||
|
||||
await fs.promises.rmdir(extensionPath, { recursive: true });
|
||||
await fs.promises.rm(extensionPath, { recursive: true });
|
||||
console.log(`Extension has been deleted at ${extensionPath}`);
|
||||
|
||||
return response.send(`Extension has been deleted at ${extensionPath}`);
|
||||
|
@ -6,6 +6,7 @@ const fs = require('fs');
|
||||
const { jsonParser, urlencodedParser } = require('../express-common');
|
||||
const { getConfigValue, mergeObjectWithYaml, excludeKeysByYaml, trimV1 } = require('../util');
|
||||
const { setAdditionalHeaders } = require('../additional-headers');
|
||||
const { OPENROUTER_HEADERS } = require('../constants');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@ -80,7 +81,7 @@ router.post('/caption-image', jsonParser, async (request, response) => {
|
||||
|
||||
if (request.body.api === 'openrouter') {
|
||||
apiUrl = 'https://openrouter.ai/api/v1/chat/completions';
|
||||
headers['HTTP-Referer'] = request.headers.referer;
|
||||
Object.assign(headers, OPENROUTER_HEADERS);
|
||||
}
|
||||
|
||||
if (request.body.api === 'openai') {
|
||||
|
@ -34,6 +34,8 @@ router.post('/serpapi', jsonParser, async (request, response) => {
|
||||
const { query } = request.body;
|
||||
const result = await fetch(`https://serpapi.com/search.json?q=${encodeURIComponent(query)}&api_key=${key}`);
|
||||
|
||||
console.log('SerpApi query', query);
|
||||
|
||||
if (!result.ok) {
|
||||
const text = await result.text();
|
||||
console.log('SerpApi request failed', result.statusText, text);
|
||||
@ -143,25 +145,38 @@ router.post('/searxng', jsonParser, async (request, response) => {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
const url = new URL(baseUrl);
|
||||
const params = new URLSearchParams();
|
||||
params.append('q', query);
|
||||
params.append('format', 'html');
|
||||
url.pathname = '/search';
|
||||
url.search = params.toString();
|
||||
console.log('SearXNG query', baseUrl, query);
|
||||
|
||||
const result = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: visitHeaders,
|
||||
});
|
||||
const mainPageUrl = new URL(baseUrl);
|
||||
const mainPageRequest = await fetch(mainPageUrl, { headers: visitHeaders });
|
||||
|
||||
if (!result.ok) {
|
||||
const text = await result.text();
|
||||
console.log('SearXNG request failed', result.statusText, text);
|
||||
if (!mainPageRequest.ok) {
|
||||
console.log('SearXNG request failed', mainPageRequest.statusText);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
|
||||
const data = await result.text();
|
||||
const mainPageText = await mainPageRequest.text();
|
||||
const clientHref = mainPageText.match(/href="(\/client.+\.css)"/)?.[1];
|
||||
|
||||
if (clientHref) {
|
||||
const clientUrl = new URL(clientHref, baseUrl);
|
||||
await fetch(clientUrl, { headers: visitHeaders });
|
||||
}
|
||||
|
||||
const searchUrl = new URL('/search', baseUrl);
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.append('q', query);
|
||||
searchUrl.search = searchParams.toString();
|
||||
|
||||
const searchResult = await fetch(searchUrl, { headers: visitHeaders });
|
||||
|
||||
if (!searchResult.ok) {
|
||||
const text = await searchResult.text();
|
||||
console.log('SearXNG request failed', searchResult.statusText, text);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
|
||||
const data = await searchResult.text();
|
||||
return response.send(data);
|
||||
} catch (error) {
|
||||
console.log('SearXNG request failed', error);
|
||||
@ -205,6 +220,8 @@ router.post('/visit', jsonParser, async (request, response) => {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
console.log('Visiting web URL', url);
|
||||
|
||||
const result = await fetch(url, { headers: visitHeaders });
|
||||
|
||||
if (!result.ok) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
const vectra = require('vectra');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const express = require('express');
|
||||
const sanitize = require('sanitize-filename');
|
||||
const { jsonParser } = require('../express-common');
|
||||
@ -440,6 +441,24 @@ router.post('/delete', jsonParser, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/purge-all', jsonParser, async (req, res) => {
|
||||
try {
|
||||
for (const source of SOURCES) {
|
||||
const sourcePath = path.join(req.user.directories.vectors, sanitize(source));
|
||||
if (!fs.existsSync(sourcePath)) {
|
||||
continue;
|
||||
}
|
||||
await fs.promises.rm(sourcePath, { recursive: true });
|
||||
console.log(`Deleted vector source store at ${sourcePath}`);
|
||||
}
|
||||
|
||||
return res.sendStatus(200);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/purge', jsonParser, async (req, res) => {
|
||||
try {
|
||||
if (!req.body.collectionId) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user