Merge branch 'staging' into model-write-in

bb1139bda94c9ab3affe03119647438103b9f4c4

Cohee <18619528+Cohee1207@users.noreply.github.com>

15 files changed, +706 -285Ignore whitespace
default/config.yaml+0 -1
@@ -118,7 +118,6 @@ extras:
118 classificationModel: Cohee/distilbert-base-uncased-go-emotions-onnx118 classificationModel: Cohee/distilbert-base-uncased-go-emotions-onnx
119 captioningModel: Xenova/vit-gpt2-image-captioning119 captioningModel: Xenova/vit-gpt2-image-captioning
120 embeddingModel: Cohee/jina-embeddings-v2-base-en120 embeddingModel: Cohee/jina-embeddings-v2-base-en
121 promptExpansionModel: Cohee/fooocus_expansion-onnx
122 speechToTextModel: Xenova/whisper-small121 speechToTextModel: Xenova/whisper-small
123 textToSpeechModel: Xenova/speecht5_tts122 textToSpeechModel: Xenova/speecht5_tts
124# -- OPENAI CONFIGURATION --123# -- OPENAI CONFIGURATION --
public/index.html+18 -16
@@ -1709,9 +1709,9 @@
1709 <div class="">1709 <div class="">
1710 <div class="inline-drawer wide100p flexFlowColumn">1710 <div class="inline-drawer wide100p flexFlowColumn">
1711 <div class="inline-drawer-toggle inline-drawer-header">1711 <div class="inline-drawer-toggle inline-drawer-header">
1712 <div class="flex-container alignItemsCenter flexNoGap">1712 <div class="flex-container alignItemsCenter">
1713 <b data-i18n="Character Names Behavior">Character Names Behavior</b>1713 <b data-i18n="Character Names Behavior">Character Names Behavior</b>
1714 <span title="Helps the model to associate messages with characters." data-i18n="[title]Helps the model to associate messages with characters." class="note-link-span fa-solid fa-circle-question"></span>1714 <span title="Helps the model to associate messages with characters." data-i18n="[title]Helps the model to associate messages with characters." class="opacity50p fa-solid fa-circle-info"></span>
1715 <small class="flexBasis100p">(<span id="character_names_display"></span>)</small>1715 <small class="flexBasis100p">(<span id="character_names_display"></span>)</small>
1716 </div>1716 </div>
1717 <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>1717 <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
@@ -1720,25 +1720,22 @@
1720 <label class="checkbox_label flexWrap alignItemsCenter" for="character_names_none">1720 <label class="checkbox_label flexWrap alignItemsCenter" for="character_names_none">
1721 <input type="radio" id="character_names_none" name="character_names" value="-1">1721 <input type="radio" id="character_names_none" name="character_names" value="-1">
1722 <span data-i18n="None">None</span>1722 <span data-i18n="None">None</span>
1723 <i class="right_menu_button fa-solid fa-circle-exclamation" title="Never add character name prefixes. May behave poorly in groups, choose with caution." data-i18n="[title]character_names_none"></i>1723 <small class="flexBasis100p" data-i18n="character_names_none">
1724 <small class="flexBasis100p" data-i18n="Never add character names.">1724 Never add character name prefixes. May behave poorly in groups, choose with caution.
1725 Never add character names.
1726 </small>1725 </small>
1727 </label>1726 </label>
1728 <label class="checkbox_label flexWrap alignItemsCenter" for="character_names_default">1727 <label class="checkbox_label flexWrap alignItemsCenter" for="character_names_default">
1729 <input type="radio" id="character_names_default" name="character_names" value="0">1728 <input type="radio" id="character_names_default" name="character_names" value="0">
1730 <span data-i18n="Default">Default</span>1729 <span data-i18n="Default">Default</span>
1731 <i class="right_menu_button fa-solid fa-circle-exclamation" title="Add prefixes for groups and past personas. Otherwise, make sure you provide names in the prompt." data-i18n="[title]character_names_default"></i>1730 <small class="flexBasis100p" data-i18n="character_names_default">
1732 <small class="flexBasis100p" data-i18n="Don't add character names unless necessary.">1731 Add prefixes for groups and past personas. Otherwise, make sure you provide names in the prompt.
1733 Don't add character names unless necessary.
1734 </small>1732 </small>
1735 </label>1733 </label>
1736 <label class="checkbox_label flexWrap alignItemsCenter" for="character_names_completion">1734 <label class="checkbox_label flexWrap alignItemsCenter" for="character_names_completion">
1737 <input type="radio" id="character_names_completion" name="character_names" value="1">1735 <input type="radio" id="character_names_completion" name="character_names" value="1">
1738 <span data-i18n="Completion Object">Completion Object</span>1736 <span data-i18n="Completion Object">Completion Object</span>
1739 <i class="right_menu_button fa-solid fa-circle-exclamation" title="Restrictions apply: only Latin alphanumerics and underscores. Doesn't work for all sources, notably: Claude, MistralAI, Google." data-i18n="[title]character_names_completion"></i>1737 <small class="flexBasis100p" data-i18n="character_names_completion">
1740 <small class="flexBasis100p" data-i18n="Add character names to completion objects.">1738 Add character names to completion objects. Restrictions apply: only Latin alphanumerics and underscores.
1741 Add character names to completion objects.
1742 </small>1739 </small>
1743 </label>1740 </label>
1744 <label class="checkbox_label flexWrap alignItemsCenter" for="character_names_content">1741 <label class="checkbox_label flexWrap alignItemsCenter" for="character_names_content">
@@ -1754,9 +1751,9 @@
1754 </div>1751 </div>
1755 <div class="inline-drawer wide100p flexFlowColumn marginBot10">1752 <div class="inline-drawer wide100p flexFlowColumn marginBot10">
1756 <div class="inline-drawer-toggle inline-drawer-header">1753 <div class="inline-drawer-toggle inline-drawer-header">
1757 <div class="flex-container alignItemsCenter flexNoGap">1754 <div class="flex-container alignItemsCenter">
1758 <b data-i18n="Continue Postfix">Continue Postfix</b>1755 <b data-i18n="Continue Postfix">Continue Postfix</b>
1759 <span data-i18n="[title]The next chunk of the continued message will be appended using this as a separator." title="The next chunk of the continued message will be appended using this as a separator." class="note-link-span fa-solid fa-circle-question"></span>1756 <span data-i18n="[title]The next chunk of the continued message will be appended using this as a separator." title="The next chunk of the continued message will be appended using this as a separator." class="opacity50p fa-solid fa-circle-info"></span>
1760 <small class="flexBasis100p">(<span id="continue_postfix_display"></span>)</small>1757 <small class="flexBasis100p">(<span id="continue_postfix_display"></span>)</small>
1761 </div>1758 </div>
1762 <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>1759 <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
@@ -4013,6 +4010,10 @@
4013 <input id="compact_input_area" type="checkbox" />4010 <input id="compact_input_area" type="checkbox" />
4014 <small data-i18n="Compact Input Area (Mobile)">Compact Input Area</small><i class="fa-solid fa-mobile-screen-button"></i>4011 <small data-i18n="Compact Input Area (Mobile)">Compact Input Area</small><i class="fa-solid fa-mobile-screen-button"></i>
4015 </label>4012 </label>
4013 <label for="show_swipe_num_all_messages" class="checkbox_label" title="Display swipe numbers for all messages, not just the last." data-i18n="[title]Display swipe numbers for all messages, not just the last.">
4014 <input id="show_swipe_num_all_messages" type="checkbox" />
4015 <small data-i18n="Swipe # for All Messages">Swipe # for All Messages</small><i class="fa-solid fa-mobile-screen-button"></i>
4016 </label>
4016 <label for="hotswapEnabled" class="checkbox_label" title="In the Character Management panel, show quick selection buttons for favorited characters." data-i18n="[title]In the Character Management panel, show quick selection buttons for favorited characters">4017 <label for="hotswapEnabled" class="checkbox_label" title="In the Character Management panel, show quick selection buttons for favorited characters." data-i18n="[title]In the Character Management panel, show quick selection buttons for favorited characters">
4017 <input id="hotswapEnabled" type="checkbox" />4018 <input id="hotswapEnabled" type="checkbox" />
4018 <small data-i18n="Characters Hotswap">Characters Hotswap</small>4019 <small data-i18n="Characters Hotswap">Characters Hotswap</small>
@@ -5941,9 +5942,10 @@
5941 </div>5942 </div>
5942 <div class="mes_bias"></div>5943 <div class="mes_bias"></div>
5943 </div>5944 </div>
5944 <div class="swipe_right fa-solid fa-chevron-right" style="display: none;">5945 <div class="flex-container swipeRightBlock flexFlowColumn flexNoGap">
5945 <div class="swipes-counter"></div>5946 <div class="swipe_right fa-solid fa-chevron-right" style="display: none;"></div>
5946 </div>5947 <div class="swipes-counter"></div>
5948 </div>
5947 </div>5949 </div>
5948 </div>5950 </div>
5949 <div id="group_avatars_template" class="template_element">5951 <div id="group_avatars_template" class="template_element">
public/script.js+34 -21
@@ -83,6 +83,7 @@ import {
83 resetMovableStyles,83 resetMovableStyles,
84 forceCharacterEditorTokenize,84 forceCharacterEditorTokenize,
85 applyPowerUserSettings,85 applyPowerUserSettings,
86 switchSwipeNumAllMessages,
86} from './scripts/power-user.js';87} from './scripts/power-user.js';
8788
88import {89import {
@@ -2369,6 +2370,13 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
23692370
2370 addCopyToCodeBlocks(newMessage);2371 addCopyToCodeBlocks(newMessage);
23712372
2373 // Set the swipes counter for past messages, only visible if 'Show Swipes on All Message' is enabled
2374 if (!params.isUser && newMessageId !== 0 && newMessageId !== chat.length - 1) {
2375 const swipesNum = chat[newMessageId].swipes?.length;
2376 const swipeId = chat[newMessageId].swipe_id + 1;
2377 newMessage.find('.swipes-counter').text(`${swipeId}\u200B/\u200b${swipesNum}`);
2378 }
2379
2372 if (showSwipes) {2380 if (showSwipes) {
2373 $('#chat .mes').last().addClass('last_mes');2381 $('#chat .mes').last().addClass('last_mes');
2374 $('#chat .mes').eq(-2).removeClass('last_mes');2382 $('#chat .mes').eq(-2).removeClass('last_mes');
@@ -4785,7 +4793,7 @@ export function removeMacros(str) {
4785 * @param {boolean} [compact] Send as a compact display message.4793 * @param {boolean} [compact] Send as a compact display message.
4786 * @param {string} [name] Name of the user sending the message. Defaults to name1.4794 * @param {string} [name] Name of the user sending the message. Defaults to name1.
4787 * @param {string} [avatar] Avatar of the user sending the message. Defaults to user_avatar.4795 * @param {string} [avatar] Avatar of the user sending the message. Defaults to user_avatar.
4788 * @returns {Promise<void>} A promise that resolves when the message is inserted.4796 * @returns {Promise<any>} A promise that resolves to the message when it is inserted.
4789 */4797 */
4790export async function sendMessageAsUser(messageText, messageBias, insertAt = null, compact = false, name = name1, avatar = user_avatar) {4798export async function sendMessageAsUser(messageText, messageBias, insertAt = null, compact = false, name = name1, avatar = user_avatar) {
4791 messageText = getRegexedString(messageText, regex_placement.USER_INPUT);4799 messageText = getRegexedString(messageText, regex_placement.USER_INPUT);
@@ -4832,6 +4840,8 @@ export async function sendMessageAsUser(messageText, messageBias, insertAt = nul
4832 await eventSource.emit(event_types.USER_MESSAGE_RENDERED, chat_id);4840 await eventSource.emit(event_types.USER_MESSAGE_RENDERED, chat_id);
4833 await saveChatConditional();4841 await saveChatConditional();
4834 }4842 }
4843
4844 return message;
4835}4845}
48364846
4837/**4847/**
@@ -7469,7 +7479,6 @@ export function showSwipeButtons() {
7469 //had to add this to make the swipe counter work7479 //had to add this to make the swipe counter work
7470 //(copied from the onclick functions for swipe buttons..7480 //(copied from the onclick functions for swipe buttons..
7471 //don't know why the array isn't set for non-swipe messsages in Generate or addOneMessage..)7481 //don't know why the array isn't set for non-swipe messsages in Generate or addOneMessage..)
7472
7473 if (chat[chat.length - 1]['swipe_id'] === undefined) { // if there is no swipe-message in the last spot of the chat array7482 if (chat[chat.length - 1]['swipe_id'] === undefined) { // if there is no swipe-message in the last spot of the chat array
7474 chat[chat.length - 1]['swipe_id'] = 0; // set it to id 07483 chat[chat.length - 1]['swipe_id'] = 0; // set it to id 0
7475 chat[chat.length - 1]['swipes'] = []; // empty the array7484 chat[chat.length - 1]['swipes'] = []; // empty the array
@@ -7479,32 +7488,36 @@ export function showSwipeButtons() {
7479 const currentMessage = $('#chat').children().filter(`[mesid="${chat.length - 1}"]`);7488 const currentMessage = $('#chat').children().filter(`[mesid="${chat.length - 1}"]`);
7480 const swipeId = chat[chat.length - 1].swipe_id;7489 const swipeId = chat[chat.length - 1].swipe_id;
7481 const swipeCounterText = (`${(swipeId + 1)}\u200B/\u200b${(chat[chat.length - 1].swipes.length)}`);7490 const swipeCounterText = (`${(swipeId + 1)}\u200B/\u200b${(chat[chat.length - 1].swipes.length)}`);
7491 const swipeRight = currentMessage.find('.swipe_right');
7492 const swipeLeft = currentMessage.find('.swipe_left');
7493 const swipeCounter = currentMessage.find('.swipes-counter');
74827494
7483 if (swipeId !== undefined && (chat[chat.length - 1].swipes.length > 1 || swipeId > 0)) {7495 if (swipeId !== undefined && (chat[chat.length - 1].swipes.length > 1 || swipeId > 0)) {
7484 currentMessage.children('.swipe_left').css('display', 'flex');7496 swipeLeft.css('display', 'flex');
7485 }7497 }
7486 //only show right when generate is off, or when next right swipe would not make a generate happen7498 //only show right when generate is off, or when next right swipe would not make a generate happen
7487 if (is_send_press === false || chat[chat.length - 1].swipes.length >= swipeId) {7499 if (is_send_press === false || chat[chat.length - 1].swipes.length >= swipeId) {
7488 currentMessage.children('.swipe_right').css('display', 'flex');7500 swipeRight.css('display', 'flex').css('opacity', '0.3');
7489 currentMessage.children('.swipe_right').css('opacity', '0.3');7501 swipeCounter.css('opacity', '0.3');
7490 }7502 }
7491 //console.log((chat[chat.length - 1]));
7492 if ((chat[chat.length - 1].swipes.length - swipeId) === 1) {7503 if ((chat[chat.length - 1].swipes.length - swipeId) === 1) {
7493 //console.log('highlighting R swipe');7504 //chevron was moved out of hardcode in HTML to class toggle dependent on last_mes or not
7494 currentMessage.children('.swipe_right').css('opacity', '0.7');7505 //necessary for 'swipe_right' div in past messages to have no chevron if 'show swipes for all messages' is turned on
7506 swipeRight.css('opacity', '0.7');
7507 swipeCounter.css('opacity', '0.7');
7495 }7508 }
7496 //console.log(swipesCounterHTML);
74977509
7498 $('.swipes-counter').text(swipeCounterText);7510 //allows for writing individual swipe counters for past messages
7511 const lastSwipeCounter = $('.last_mes .swipes-counter');
7512 lastSwipeCounter.text(swipeCounterText).show();
74997513
7500 //console.log(swipeId);7514 switchSwipeNumAllMessages();
7501 //console.log(chat[chat.length - 1].swipes.length);
7502}7515}
75037516
7504export function hideSwipeButtons() {7517export function hideSwipeButtons() {
7505 //console.log('hideswipebuttons entered');7518 $('#chat').find('.swipe_right').hide();
7506 $('#chat').find('.swipe_right').css('display', 'none');7519 $('#chat').find('.last_mes .swipes-counter').hide();
7507 $('#chat').find('.swipe_left').css('display', 'none');7520 $('#chat').find('.swipe_left').hide();
7508}7521}
75097522
7510/**7523/**
@@ -8339,8 +8352,8 @@ const swipe_right = () => {
8339 }8352 }
83408353
8341 const currentMessage = $('#chat').children().filter(`[mesid="${chat.length - 1}"]`);8354 const currentMessage = $('#chat').children().filter(`[mesid="${chat.length - 1}"]`);
8342 let this_div = currentMessage.children('.swipe_right');8355 let this_div = currentMessage.find('.swipe_right');
8343 let this_mes_div = this_div.parent();8356 let this_mes_div = this_div.parent().parent();
83448357
8345 if (chat[chat.length - 1]['swipe_id'] > chat[chat.length - 1]['swipes'].length) { //if we swipe right while generating (the swipe ID is greater than what we are viewing now)8358 if (chat[chat.length - 1]['swipe_id'] > chat[chat.length - 1]['swipes'].length) { //if we swipe right while generating (the swipe ID is greater than what we are viewing now)
8346 chat[chat.length - 1]['swipe_id'] = chat[chat.length - 1]['swipes'].length; //show that message slot (will be '...' while generating)8359 chat[chat.length - 1]['swipe_id'] = chat[chat.length - 1]['swipes'].length; //show that message slot (will be '...' while generating)
@@ -8350,7 +8363,7 @@ const swipe_right = () => {
8350 }8363 }
8351 // handles animated transitions when swipe right, specifically height transitions between messages8364 // handles animated transitions when swipe right, specifically height transitions between messages
8352 if (run_generate || run_swipe_right) {8365 if (run_generate || run_swipe_right) {
8353 let this_mes_block = this_mes_div.children('.mes_block').children('.mes_text');8366 let this_mes_block = this_mes_div.find('.mes_block .mes_text');
8354 const this_mes_div_height = this_mes_div[0].scrollHeight;8367 const this_mes_div_height = this_mes_div[0].scrollHeight;
8355 const this_mes_block_height = this_mes_block[0].scrollHeight;8368 const this_mes_block_height = this_mes_block[0].scrollHeight;
83568369
@@ -9407,9 +9420,9 @@ jQuery(async function () {
94079420
9408 ///// SWIPE BUTTON CLICKS ///////9421 ///// SWIPE BUTTON CLICKS ///////
94099422
9410 $(document).on('click', '.swipe_right', swipe_right);9423 //limit swiping to only last message clicks
94119424 $(document).on('click', '.last_mes .swipe_right', swipe_right);
9412 $(document).on('click', '.swipe_left', swipe_left);9425 $(document).on('click', '.last_mes .swipe_left', swipe_left);
94139426
9414 const debouncedCharacterSearch = debounce((searchQuery) => {9427 const debouncedCharacterSearch = debounce((searchQuery) => {
9415 entitiesFilter.setFilterData(FILTER_TYPES.SEARCH, searchQuery);9428 entitiesFilter.setFilterData(FILTER_TYPES.SEARCH, searchQuery);
public/scripts/extensions/expressions/index.js+33 -7
@@ -12,6 +12,8 @@ import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '
12import { isFunctionCallingSupported } from '../../openai.js';12import { isFunctionCallingSupported } from '../../openai.js';
13import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js';13import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js';
14import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';14import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
15import { slashCommandReturnHelper } from '../../slash-commands/SlashCommandReturnHelper.js';
16import { SlashCommandClosure } from '../../slash-commands/SlashCommandClosure.js';
15export { MODULE_NAME };17export { MODULE_NAME };
1618
17const MODULE_NAME = 'expressions';19const MODULE_NAME = 'expressions';
@@ -2134,18 +2136,42 @@ function migrateSettings() {
2134 name: 'classify-expressions',2136 name: 'classify-expressions',
2135 aliases: ['expressions'],2137 aliases: ['expressions'],
2136 callback: async (args) => {2138 callback: async (args) => {
2137 const list = await getExpressionsList();2139 /** @type {import('../../slash-commands/SlashCommandReturnHelper.js').SlashCommandReturnType} */
2138 switch (String(args.format).toLowerCase()) {2140 // @ts-ignore
2139 case 'json':2141 let returnType = args.return;
2140 return JSON.stringify(list);2142
2141 default:2143 // Old legacy return type handling
2142 return list.join(', ');2144 if (args.format) {
2145 toastr.warning(`Legacy argument 'format' with value '${args.format}' is deprecated. Please use 'return' instead. Routing to the correct return type...`, 'Deprecation warning');
2146 const type = String(args?.format).toLowerCase().trim();
2147 switch (type) {
2148 case 'json':
2149 returnType = 'object';
2150 break;
2151 default:
2152 returnType = 'pipe';
2153 break;
2154 }
2143 }2155 }
2156
2157 // Now the actual new return type handling
2158 const list = await getExpressionsList();
2159
2160 return await slashCommandReturnHelper.doReturn(returnType ?? 'pipe', list, { objectToStringFunc: list => list.join(', ') });
2144 },2161 },
2145 namedArgumentList: [2162 namedArgumentList: [
2146 SlashCommandNamedArgument.fromProps({2163 SlashCommandNamedArgument.fromProps({
2164 name: 'return',
2165 description: 'The way how you want the return value to be provided',
2166 typeList: [ARGUMENT_TYPE.STRING],
2167 defaultValue: 'pipe',
2168 enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
2169 forceEnum: true,
2170 }),
2171 // TODO remove some day
2172 SlashCommandNamedArgument.fromProps({
2147 name: 'format',2173 name: 'format',
2148 description: 'The format to return the list in: comma-separated plain text or JSON array. Default is plain text.',2174 description: '!!! DEPRECATED - use "return" instead !!! The format to return the list in: comma-separated plain text or JSON array. Default is plain text.',
2149 typeList: [ARGUMENT_TYPE.STRING],2175 typeList: [ARGUMENT_TYPE.STRING],
2150 enumList: [2176 enumList: [
2151 new SlashCommandEnumValue('plain', null, enumTypes.enum, ', '),2177 new SlashCommandEnumValue('plain', null, enumTypes.enum, ', '),
public/scripts/extensions/stable-diffusion/index.js+326 -59
@@ -20,7 +20,7 @@ import {
20} from '../../../script.js';20} from '../../../script.js';
21import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules, renderExtensionTemplateAsync, writeExtensionField } from '../../extensions.js';21import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules, renderExtensionTemplateAsync, writeExtensionField } from '../../extensions.js';
22import { selected_group } from '../../group-chats.js';22import { selected_group } from '../../group-chats.js';
23import { stringFormat, initScrollHeight, resetScrollHeight, getCharaFilename, saveBase64AsFile, getBase64Async, delay, isTrueBoolean, debounce } from '../../utils.js';23import { stringFormat, initScrollHeight, resetScrollHeight, getCharaFilename, saveBase64AsFile, getBase64Async, delay, isTrueBoolean, debounce, isFalseBoolean } from '../../utils.js';
24import { getMessageTimeStamp, humanizedDateTime } from '../../RossAscends-mods.js';24import { getMessageTimeStamp, humanizedDateTime } from '../../RossAscends-mods.js';
25import { SECRET_KEYS, secret_state, writeSecret } from '../../secrets.js';25import { SECRET_KEYS, secret_state, writeSecret } from '../../secrets.js';
26import { getNovelUnlimitedImageGeneration, getNovelAnlas, loadNovelSubscriptionData } from '../../nai-settings.js';26import { getNovelUnlimitedImageGeneration, getNovelAnlas, loadNovelSubscriptionData } from '../../nai-settings.js';
@@ -31,6 +31,7 @@ import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '
31import { debounce_timeout } from '../../constants.js';31import { debounce_timeout } from '../../constants.js';
32import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';32import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';
33import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js';33import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js';
34import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
34export { MODULE_NAME };35export { MODULE_NAME };
3536
36const MODULE_NAME = 'sd';37const MODULE_NAME = 'sd';
@@ -221,7 +222,6 @@ const defaultSettings = {
221222
222 // Refine mode223 // Refine mode
223 refine_mode: false,224 refine_mode: false,
224 expand: false,
225 interactive_mode: false,225 interactive_mode: false,
226 multimodal_captioning: false,226 multimodal_captioning: false,
227 snap: false,227 snap: false,
@@ -240,7 +240,7 @@ const defaultSettings = {
240 drawthings_auth: '',240 drawthings_auth: '',
241241
242 hr_upscaler: 'Latent',242 hr_upscaler: 'Latent',
243 hr_scale: 2.0,243 hr_scale: 1.0,
244 hr_scale_min: 1.0,244 hr_scale_min: 1.0,
245 hr_scale_max: 4.0,245 hr_scale_max: 4.0,
246 hr_scale_step: 0.1,246 hr_scale_step: 0.1,
@@ -260,10 +260,6 @@ const defaultSettings = {
260 clip_skip: 1,260 clip_skip: 1,
261261
262 // NovelAI settings262 // NovelAI settings
263 novel_upscale_ratio_min: 1.0,
264 novel_upscale_ratio_max: 4.0,
265 novel_upscale_ratio_step: 0.1,
266 novel_upscale_ratio: 1.0,
267 novel_anlas_guard: false,263 novel_anlas_guard: false,
268 novel_sm: false,264 novel_sm: false,
269 novel_sm_dyn: false,265 novel_sm_dyn: false,
@@ -416,7 +412,6 @@ async function loadSettings() {
416 $('#sd_hr_scale').val(extension_settings.sd.hr_scale).trigger('input');412 $('#sd_hr_scale').val(extension_settings.sd.hr_scale).trigger('input');
417 $('#sd_denoising_strength').val(extension_settings.sd.denoising_strength).trigger('input');413 $('#sd_denoising_strength').val(extension_settings.sd.denoising_strength).trigger('input');
418 $('#sd_hr_second_pass_steps').val(extension_settings.sd.hr_second_pass_steps).trigger('input');414 $('#sd_hr_second_pass_steps').val(extension_settings.sd.hr_second_pass_steps).trigger('input');
419 $('#sd_novel_upscale_ratio').val(extension_settings.sd.novel_upscale_ratio).trigger('input');
420 $('#sd_novel_anlas_guard').prop('checked', extension_settings.sd.novel_anlas_guard);415 $('#sd_novel_anlas_guard').prop('checked', extension_settings.sd.novel_anlas_guard);
421 $('#sd_novel_sm').prop('checked', extension_settings.sd.novel_sm);416 $('#sd_novel_sm').prop('checked', extension_settings.sd.novel_sm);
422 $('#sd_novel_sm_dyn').prop('checked', extension_settings.sd.novel_sm_dyn);417 $('#sd_novel_sm_dyn').prop('checked', extension_settings.sd.novel_sm_dyn);
@@ -430,7 +425,6 @@ async function loadSettings() {
430 $('#sd_restore_faces').prop('checked', extension_settings.sd.restore_faces);425 $('#sd_restore_faces').prop('checked', extension_settings.sd.restore_faces);
431 $('#sd_enable_hr').prop('checked', extension_settings.sd.enable_hr);426 $('#sd_enable_hr').prop('checked', extension_settings.sd.enable_hr);
432 $('#sd_refine_mode').prop('checked', extension_settings.sd.refine_mode);427 $('#sd_refine_mode').prop('checked', extension_settings.sd.refine_mode);
433 $('#sd_expand').prop('checked', extension_settings.sd.expand);
434 $('#sd_multimodal_captioning').prop('checked', extension_settings.sd.multimodal_captioning);428 $('#sd_multimodal_captioning').prop('checked', extension_settings.sd.multimodal_captioning);
435 $('#sd_auto_url').val(extension_settings.sd.auto_url);429 $('#sd_auto_url').val(extension_settings.sd.auto_url);
436 $('#sd_auto_auth').val(extension_settings.sd.auto_auth);430 $('#sd_auto_auth').val(extension_settings.sd.auto_auth);
@@ -644,37 +638,13 @@ async function onSaveStyleClick() {
644 saveSettingsDebounced();638 saveSettingsDebounced();
645}639}
646640
647async function expandPrompt(prompt) {
648 try {
649 const response = await fetch('/api/sd/expand', {
650 method: 'POST',
651 headers: getRequestHeaders(),
652 body: JSON.stringify({ prompt: prompt }),
653 });
654
655 if (!response.ok) {
656 throw new Error('API returned an error.');
657 }
658
659 const data = await response.json();
660 return data.prompt;
661 } catch {
662 return prompt;
663 }
664}
665
666/**641/**
667 * Modifies prompt based on auto-expansion and user inputs.642 * Modifies prompt based on user inputs.
668 * @param {string} prompt Prompt to refine643 * @param {string} prompt Prompt to refine
669 * @param {boolean} allowExpand Whether to allow auto-expansion
670 * @param {boolean} isNegative Whether the prompt is a negative one644 * @param {boolean} isNegative Whether the prompt is a negative one
671 * @returns {Promise<string>} Refined prompt645 * @returns {Promise<string>} Refined prompt
672 */646 */
673async function refinePrompt(prompt, allowExpand, isNegative = false) {647async function refinePrompt(prompt, isNegative) {
674 if (allowExpand && extension_settings.sd.expand) {
675 prompt = await expandPrompt(prompt);
676 }
677
678 if (extension_settings.sd.refine_mode) {648 if (extension_settings.sd.refine_mode) {
679 const text = isNegative ? '<h3>Review and edit the <i>negative</i> prompt:</h3>' : '<h3>Review and edit the prompt:</h3>';649 const text = isNegative ? '<h3>Review and edit the <i>negative</i> prompt:</h3>' : '<h3>Review and edit the prompt:</h3>';
680 const refinedPrompt = await callGenericPopup(text + 'Press "Cancel" to abort the image generation.', POPUP_TYPE.INPUT, prompt.trim(), { rows: 5, okButton: 'Continue' });650 const refinedPrompt = await callGenericPopup(text + 'Press "Cancel" to abort the image generation.', POPUP_TYPE.INPUT, prompt.trim(), { rows: 5, okButton: 'Continue' });
@@ -800,11 +770,6 @@ function combinePrefixes(str1, str2, macro = '') {
800 return process(result);770 return process(result);
801}771}
802772
803function onExpandInput() {
804 extension_settings.sd.expand = !!$(this).prop('checked');
805 saveSettingsDebounced();
806}
807
808function onRefineModeInput() {773function onRefineModeInput() {
809 extension_settings.sd.refine_mode = !!$('#sd_refine_mode').prop('checked');774 extension_settings.sd.refine_mode = !!$('#sd_refine_mode').prop('checked');
810 saveSettingsDebounced();775 saveSettingsDebounced();
@@ -969,12 +934,6 @@ async function onViewAnlasClick() {
969 toastr.info(`Free image generation: ${unlimitedGeneration ? 'Yes' : 'No'}`, `Anlas: ${anlas}`);934 toastr.info(`Free image generation: ${unlimitedGeneration ? 'Yes' : 'No'}`, `Anlas: ${anlas}`);
970}935}
971936
972function onNovelUpscaleRatioInput() {
973 extension_settings.sd.novel_upscale_ratio = Number($('#sd_novel_upscale_ratio').val());
974 $('#sd_novel_upscale_ratio_value').val(extension_settings.sd.novel_upscale_ratio.toFixed(1));
975 saveSettingsDebounced();
976}
977
978function onNovelAnlasGuardInput() {937function onNovelAnlasGuardInput() {
979 extension_settings.sd.novel_anlas_guard = !!$('#sd_novel_anlas_guard').prop('checked');938 extension_settings.sd.novel_anlas_guard = !!$('#sd_novel_anlas_guard').prop('checked');
980 saveSettingsDebounced();939 saveSettingsDebounced();
@@ -2273,6 +2232,25 @@ function getRawLastMessage() {
2273}2232}
22742233
2275/**2234/**
2235 * Ensure that the selected option exists in the dropdown.
2236 * @param {string} setting Setting key
2237 * @param {string} selector Dropdown selector
2238 * @returns {void}
2239 */
2240function ensureSelectionExists(setting, selector) {
2241 /** @type {HTMLSelectElement} */
2242 const selectElement = document.querySelector(selector);
2243 if (!selectElement) {
2244 return;
2245 }
2246 const options = Array.from(selectElement.options);
2247 const value = extension_settings.sd[setting];
2248 if (selectElement.selectedOptions.length && !options.some(option => option.value === value)) {
2249 extension_settings.sd[setting] = selectElement.selectedOptions[0].value;
2250 }
2251}
2252
2253/**
2276 * Generates an image based on the given trigger word.2254 * Generates an image based on the given trigger word.
2277 * @param {string} initiator The initiator of the image generation2255 * @param {string} initiator The initiator of the image generation
2278 * @param {Record<string, object>} args Command arguments2256 * @param {Record<string, object>} args Command arguments
@@ -2292,8 +2270,8 @@ async function generatePicture(initiator, args, trigger, message, callback) {
2292 return;2270 return;
2293 }2271 }
22942272
2295 extension_settings.sd.sampler = $('#sd_sampler').find(':selected').val();2273 ensureSelectionExists('sampler', '#sd_sampler');
2296 extension_settings.sd.model = $('#sd_model').find(':selected').val();2274 ensureSelectionExists('model', '#sd_model');
22972275
2298 trigger = trigger.trim();2276 trigger = trigger.trim();
2299 const generationType = getGenerationType(trigger);2277 const generationType = getGenerationType(trigger);
@@ -2441,7 +2419,7 @@ async function getPrompt(generationType, message, trigger, quietPrompt, combineN
2441 }2419 }
24422420
2443 if (generationType !== generationMode.FREE) {2421 if (generationType !== generationMode.FREE) {
2444 prompt = await refinePrompt(prompt, true);2422 prompt = await refinePrompt(prompt, false);
2445 }2423 }
24462424
2447 return prompt;2425 return prompt;
@@ -2469,7 +2447,7 @@ function generateFreeModePrompt(trigger, combineNegatives) {
2469 return message.original_avatar.replace(/\.[^/.]+$/, '');2447 return message.original_avatar.replace(/\.[^/.]+$/, '');
2470 }2448 }
2471 }2449 }
2472 throw new Error('No usable messages found.');2450 return '';
2473 };2451 };
24742452
2475 const key = getLastCharacterKey();2453 const key = getLastCharacterKey();
@@ -3031,7 +3009,7 @@ async function generateNovelImage(prompt, negativePrompt, signal) {
3031 width: width,3009 width: width,
3032 height: height,3010 height: height,
3033 negative_prompt: negativePrompt,3011 negative_prompt: negativePrompt,
3034 upscale_ratio: extension_settings.sd.novel_upscale_ratio,3012 upscale_ratio: extension_settings.sd.hr_scale,
3035 decrisper: extension_settings.sd.novel_decrisper,3013 decrisper: extension_settings.sd.novel_decrisper,
3036 sm: sm,3014 sm: sm,
3037 sm_dyn: sm_dyn,3015 sm_dyn: sm_dyn,
@@ -3613,8 +3591,8 @@ async function sdMessageButton(e) {
3613 try {3591 try {
3614 setBusyIcon(true);3592 setBusyIcon(true);
3615 if (hasSavedImage) {3593 if (hasSavedImage) {
3616 const prompt = await refinePrompt(message.extra.title, false, false);3594 const prompt = await refinePrompt(message.extra.title, false);
3617 const negative = hasSavedNegative ? await refinePrompt(message.extra.negative, false, true) : '';3595 const negative = hasSavedNegative ? await refinePrompt(message.extra.negative, true) : '';
3618 message.extra.title = prompt;3596 message.extra.title = prompt;
36193597
3620 const generationType = message?.extra?.generationType ?? generationMode.FREE;3598 const generationType = message?.extra?.generationType ?? generationMode.FREE;
@@ -3756,8 +3734,8 @@ async function onImageSwiped({ message, element, direction }) {
3756 eventSource.once(CUSTOM_STOP_EVENT, stopListener);3734 eventSource.once(CUSTOM_STOP_EVENT, stopListener);
3757 const callback = () => { };3735 const callback = () => { };
3758 const hasNegative = message.extra.negative;3736 const hasNegative = message.extra.negative;
3759 const prompt = await refinePrompt(message.extra.title, false, false);3737 const prompt = await refinePrompt(message.extra.title, false);
3760 const negativePromptPrefix = hasNegative ? await refinePrompt(message.extra.negative, false, true) : '';3738 const negativePromptPrefix = hasNegative ? await refinePrompt(message.extra.negative, true) : '';
3761 const characterName = context.groupId3739 const characterName = context.groupId
3762 ? context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]]?.id?.toString()3740 ? context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]]?.id?.toString()
3763 : context.characters[context.characterId]?.name;3741 : context.characters[context.characterId]?.name;
@@ -3788,12 +3766,85 @@ async function onImageSwiped({ message, element, direction }) {
3788 await context.saveChat();3766 await context.saveChat();
3789}3767}
37903768
3769/**
3770 * Applies the command arguments to the extension settings.
3771 * @typedef {import('../../slash-commands/SlashCommand.js').NamedArguments} NamedArguments
3772 * @typedef {import('../../slash-commands/SlashCommand.js').NamedArgumentsCapture} NamedArgumentsCapture
3773 * @param {NamedArguments | NamedArgumentsCapture} args - Command arguments
3774 * @returns {Record<string, any>} - Current settings before applying the command arguments
3775 */
3776function applyCommandArguments(args) {
3777 const overrideSettings = {};
3778 const currentSettings = {};
3779 const settingMap = {
3780 'edit': 'refine_mode',
3781 'extend': 'free_extend',
3782 'multimodal': 'multimodal_captioning',
3783 'seed': 'seed',
3784 'width': 'width',
3785 'height': 'height',
3786 'steps': 'steps',
3787 'cfg': 'scale',
3788 'skip': 'clip_skip',
3789 'model': 'model',
3790 'sampler': 'sampler',
3791 'scheduler': 'scheduler',
3792 'vae': 'vae',
3793 'upscaler': 'hr_upscaler',
3794 'scale': 'hr_scale',
3795 'hires': 'enable_hr',
3796 'denoise': 'denoising_strength',
3797 '2ndpass': 'hr_second_pass_steps',
3798 'faces': 'restore_faces',
3799 };
3800
3801 for (const [param, setting] of Object.entries(settingMap)) {
3802 if (args[param] === undefined || defaultSettings[setting] === undefined) {
3803 continue;
3804 }
3805 currentSettings[setting] = extension_settings.sd[setting];
3806 const value = String(args[param]);
3807 const type = typeof defaultSettings[setting];
3808 switch (type) {
3809 case 'boolean':
3810 overrideSettings[setting] = isTrueBoolean(value) || !isFalseBoolean(value);
3811 break;
3812 case 'number':
3813 overrideSettings[setting] = Number(value);
3814 break;
3815 default:
3816 overrideSettings[setting] = value;
3817 break;
3818 }
3819 }
3820
3821 Object.assign(extension_settings.sd, overrideSettings);
3822 return currentSettings;
3823}
3824
3791jQuery(async () => {3825jQuery(async () => {
3792 await addSDGenButtons();3826 await addSDGenButtons();
37933827
3828 const getSelectEnumProvider = (id, text) => () => Array.from(document.querySelectorAll(`#${id} > [value]`)).map(x => new SlashCommandEnumValue(x.getAttribute('value'), text ? x.textContent : null));
3829
3794 SlashCommandParser.addCommandObject(SlashCommand.fromProps({3830 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
3795 name: 'imagine',3831 name: 'imagine',
3796 callback: (args, trigger) => generatePicture(initiators.command, args, String(trigger)),3832 returns: 'URL of the generated image, or an empty string if the generation failed',
3833 callback: async (args, trigger) => {
3834 const currentSettings = applyCommandArguments(args);
3835
3836 try {
3837 return await generatePicture(initiators.command, args, String(trigger));
3838 } catch (error) {
3839 console.error('Failed to generate image:', error);
3840 return '';
3841 } finally {
3842 if (Object.keys(currentSettings).length) {
3843 Object.assign(extension_settings.sd, currentSettings);
3844 saveSettingsDebounced();
3845 }
3846 }
3847 },
3797 aliases: ['sd', 'img', 'image'],3848 aliases: ['sd', 'img', 'image'],
3798 namedArgumentList: [3849 namedArgumentList: [
3799 new SlashCommandNamedArgument(3850 new SlashCommandNamedArgument(
@@ -3803,6 +3854,164 @@ jQuery(async () => {
3803 name: 'negative',3854 name: 'negative',
3804 description: 'negative prompt prefix',3855 description: 'negative prompt prefix',
3805 typeList: [ARGUMENT_TYPE.STRING],3856 typeList: [ARGUMENT_TYPE.STRING],
3857 isRequired: false,
3858 acceptsMultiple: false,
3859 }),
3860 SlashCommandNamedArgument.fromProps({
3861 name: 'extend',
3862 description: 'auto-extend free mode prompts with the LLM',
3863 typeList: [ARGUMENT_TYPE.BOOLEAN],
3864 enumProvider: commonEnumProviders.boolean('trueFalse'),
3865 isRequired: false,
3866 acceptsMultiple: false,
3867 }),
3868 SlashCommandNamedArgument.fromProps({
3869 name: 'edit',
3870 description: 'edit the prompt before generation',
3871 typeList: [ARGUMENT_TYPE.BOOLEAN],
3872 enumProvider: commonEnumProviders.boolean('trueFalse'),
3873 isRequired: false,
3874 acceptsMultiple: false,
3875 }),
3876 SlashCommandNamedArgument.fromProps({
3877 name: 'multimodal',
3878 description: 'use multimodal captioning (for portraits only)',
3879 typeList: [ARGUMENT_TYPE.BOOLEAN],
3880 enumProvider: commonEnumProviders.boolean('trueFalse'),
3881 isRequired: false,
3882 acceptsMultiple: false,
3883 }),
3884 SlashCommandNamedArgument.fromProps({
3885 name: 'snap',
3886 description: 'snap auto-adjusted dimensions to the nearest known resolution (portraits and backgrounds only)',
3887 typeList: [ARGUMENT_TYPE.BOOLEAN],
3888 enumProvider: commonEnumProviders.boolean('trueFalse'),
3889 isRequired: false,
3890 acceptsMultiple: false,
3891 }),
3892 SlashCommandNamedArgument.fromProps({
3893 name: 'seed',
3894 description: 'random seed',
3895 isRequired: false,
3896 typeList: [ARGUMENT_TYPE.NUMBER],
3897 acceptsMultiple: false,
3898 }),
3899 SlashCommandNamedArgument.fromProps({
3900 name: 'width',
3901 description: 'image width',
3902 isRequired: false,
3903 typeList: [ARGUMENT_TYPE.NUMBER],
3904 acceptsMultiple: false,
3905 }),
3906 SlashCommandNamedArgument.fromProps({
3907 name: 'height',
3908 description: 'image height',
3909 isRequired: false,
3910 typeList: [ARGUMENT_TYPE.NUMBER],
3911 acceptsMultiple: false,
3912 }),
3913 SlashCommandNamedArgument.fromProps({
3914 name: 'steps',
3915 description: 'number of steps',
3916 isRequired: false,
3917 typeList: [ARGUMENT_TYPE.NUMBER],
3918 acceptsMultiple: false,
3919 }),
3920 SlashCommandNamedArgument.fromProps({
3921 name: 'cfg',
3922 description: 'CFG scale',
3923 isRequired: false,
3924 typeList: [ARGUMENT_TYPE.NUMBER],
3925 acceptsMultiple: false,
3926 }),
3927 SlashCommandNamedArgument.fromProps({
3928 name: 'skip',
3929 description: 'CLIP skip layers',
3930 isRequired: false,
3931 typeList: [ARGUMENT_TYPE.NUMBER],
3932 acceptsMultiple: false,
3933 }),
3934 SlashCommandNamedArgument.fromProps({
3935 name: 'model',
3936 description: 'model override',
3937 isRequired: false,
3938 typeList: [ARGUMENT_TYPE.STRING],
3939 acceptsMultiple: false,
3940 forceEnum: true,
3941 enumProvider: getSelectEnumProvider('sd_model', true),
3942 }),
3943 SlashCommandNamedArgument.fromProps({
3944 name: 'sampler',
3945 description: 'sampler override',
3946 isRequired: false,
3947 typeList: [ARGUMENT_TYPE.STRING],
3948 acceptsMultiple: false,
3949 forceEnum: true,
3950 enumProvider: getSelectEnumProvider('sd_sampler', false),
3951 }),
3952 SlashCommandNamedArgument.fromProps({
3953 name: 'scheduler',
3954 description: 'scheduler override',
3955 isRequired: false,
3956 typeList: [ARGUMENT_TYPE.STRING],
3957 acceptsMultiple: false,
3958 forceEnum: true,
3959 enumProvider: getSelectEnumProvider('sd_scheduler', false),
3960 }),
3961 SlashCommandNamedArgument.fromProps({
3962 name: 'vae',
3963 description: 'VAE name override',
3964 isRequired: false,
3965 typeList: [ARGUMENT_TYPE.STRING],
3966 acceptsMultiple: false,
3967 forceEnum: true,
3968 enumProvider: getSelectEnumProvider('sd_vae', false),
3969 }),
3970 SlashCommandNamedArgument.fromProps({
3971 name: 'upscaler',
3972 description: 'upscaler override',
3973 isRequired: false,
3974 typeList: [ARGUMENT_TYPE.STRING],
3975 acceptsMultiple: false,
3976 forceEnum: true,
3977 enumProvider: getSelectEnumProvider('sd_hr_upscaler', false),
3978 }),
3979 SlashCommandNamedArgument.fromProps({
3980 name: 'hires',
3981 description: 'enable high-res fix',
3982 isRequired: false,
3983 typeList: [ARGUMENT_TYPE.BOOLEAN],
3984 acceptsMultiple: false,
3985 enumProvider: commonEnumProviders.boolean('trueFalse'),
3986 }),
3987 SlashCommandNamedArgument.fromProps({
3988 name: 'scale',
3989 description: 'upscale amount',
3990 isRequired: false,
3991 typeList: [ARGUMENT_TYPE.NUMBER],
3992 acceptsMultiple: false,
3993 }),
3994 SlashCommandNamedArgument.fromProps({
3995 name: 'denoise',
3996 description: 'denoising strength',
3997 isRequired: false,
3998 typeList: [ARGUMENT_TYPE.NUMBER],
3999 acceptsMultiple: false,
4000 }),
4001 SlashCommandNamedArgument.fromProps({
4002 name: '2ndpass',
4003 description: 'second pass steps',
4004 isRequired: false,
4005 typeList: [ARGUMENT_TYPE.NUMBER],
4006 acceptsMultiple: false,
4007 }),
4008 SlashCommandNamedArgument.fromProps({
4009 name: 'faces',
4010 description: 'restore faces',
4011 isRequired: false,
4012 typeList: [ARGUMENT_TYPE.BOOLEAN],
4013 acceptsMultiple: false,
4014 enumProvider: commonEnumProviders.boolean('trueFalse'),
3806 }),4015 }),
3807 ],4016 ],
3808 unnamedArgumentList: [4017 unnamedArgumentList: [
@@ -3824,6 +4033,66 @@ jQuery(async () => {
3824 }));4033 }));
38254034
3826 SlashCommandParser.addCommandObject(SlashCommand.fromProps({4035 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
4036 name: 'imagine-source',
4037 aliases: ['sd-source', 'img-source'],
4038 returns: 'a name of the current generation source',
4039 unnamedArgumentList: [
4040 SlashCommandArgument.fromProps({
4041 description: 'source name',
4042 typeList: [ARGUMENT_TYPE.STRING],
4043 isRequired: false,
4044 forceEnum: true,
4045 enumProvider: getSelectEnumProvider('sd_source', true),
4046 }),
4047 ],
4048 helpString: 'If an argument is provided, change the source of the image generation, e.g. <code>/imagine-source comfy</code>. Returns the current source.',
4049 callback: async (_args, name) => {
4050 if (!name) {
4051 return extension_settings.sd.source;
4052 }
4053 const isKnownSource = Object.keys(sources).includes(String(name));
4054 if (!isKnownSource) {
4055 throw new Error('The value provided is not a valid image generation source.');
4056 }
4057 const option = document.querySelector(`#sd_source [value="${name}"]`);
4058 if (!(option instanceof HTMLOptionElement)) {
4059 throw new Error('Could not find the source option in the dropdown.');
4060 }
4061 option.selected = true;
4062 await onSourceChange();
4063 return extension_settings.sd.source;
4064 },
4065 }));
4066
4067 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
4068 name: 'imagine-style',
4069 aliases: ['sd-style', 'img-style'],
4070 returns: 'a name of the current style',
4071 unnamedArgumentList: [
4072 SlashCommandArgument.fromProps({
4073 description: 'style name',
4074 typeList: [ARGUMENT_TYPE.STRING],
4075 isRequired: false,
4076 forceEnum: true,
4077 enumProvider: getSelectEnumProvider('sd_style', false),
4078 }),
4079 ],
4080 helpString: 'If an argument is provided, change the style of the image generation, e.g. <code>/imagine-style MyStyle</code>. Returns the current style.',
4081 callback: async (_args, name) => {
4082 if (!name) {
4083 return extension_settings.sd.style;
4084 }
4085 const option = document.querySelector(`#sd_style [value="${name}"]`);
4086 if (!(option instanceof HTMLOptionElement)) {
4087 throw new Error('Could not find the style option in the dropdown.');
4088 }
4089 option.selected = true;
4090 onStyleSelect();
4091 return extension_settings.sd.style;
4092 },
4093 }));
4094
4095 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
3827 name: 'imagine-comfy-workflow',4096 name: 'imagine-comfy-workflow',
3828 callback: changeComfyWorkflow,4097 callback: changeComfyWorkflow,
3829 aliases: ['icw'],4098 aliases: ['icw'],
@@ -3832,7 +4101,7 @@ jQuery(async () => {
3832 description: 'workflow name',4101 description: 'workflow name',
3833 typeList: [ARGUMENT_TYPE.STRING],4102 typeList: [ARGUMENT_TYPE.STRING],
3834 isRequired: true,4103 isRequired: true,
3835 enumProvider: () => Array.from(document.querySelectorAll('#sd_comfy_workflow > [value]')).map(x => x.getAttribute('value')).map(workflow => new SlashCommandEnumValue(workflow)),4104 enumProvider: getSelectEnumProvider('sd_comfy_workflow', false),
3836 }),4105 }),
3837 ],4106 ],
3838 helpString: '(workflowName) - change the workflow to be used for image generation with ComfyUI, e.g. <pre><code>/imagine-comfy-workflow MyWorkflow</code></pre>',4107 helpString: '(workflowName) - change the workflow to be used for image generation with ComfyUI, e.g. <pre><code>/imagine-comfy-workflow MyWorkflow</code></pre>',
@@ -3874,7 +4143,6 @@ jQuery(async () => {
3874 $('#sd_hr_scale').on('input', onHrScaleInput);4143 $('#sd_hr_scale').on('input', onHrScaleInput);
3875 $('#sd_denoising_strength').on('input', onDenoisingStrengthInput);4144 $('#sd_denoising_strength').on('input', onDenoisingStrengthInput);
3876 $('#sd_hr_second_pass_steps').on('input', onHrSecondPassStepsInput);4145 $('#sd_hr_second_pass_steps').on('input', onHrSecondPassStepsInput);
3877 $('#sd_novel_upscale_ratio').on('input', onNovelUpscaleRatioInput);
3878 $('#sd_novel_anlas_guard').on('input', onNovelAnlasGuardInput);4146 $('#sd_novel_anlas_guard').on('input', onNovelAnlasGuardInput);
3879 $('#sd_novel_view_anlas').on('click', onViewAnlasClick);4147 $('#sd_novel_view_anlas').on('click', onViewAnlasClick);
3880 $('#sd_novel_sm').on('input', onNovelSmInput);4148 $('#sd_novel_sm').on('input', onNovelSmInput);
@@ -3887,7 +4155,6 @@ jQuery(async () => {
3887 $('#sd_comfy_open_workflow_editor').on('click', onComfyOpenWorkflowEditorClick);4155 $('#sd_comfy_open_workflow_editor').on('click', onComfyOpenWorkflowEditorClick);
3888 $('#sd_comfy_new_workflow').on('click', onComfyNewWorkflowClick);4156 $('#sd_comfy_new_workflow').on('click', onComfyNewWorkflowClick);
3889 $('#sd_comfy_delete_workflow').on('click', onComfyDeleteWorkflowClick);4157 $('#sd_comfy_delete_workflow').on('click', onComfyDeleteWorkflowClick);
3890 $('#sd_expand').on('input', onExpandInput);
3891 $('#sd_style').on('change', onStyleSelect);4158 $('#sd_style').on('change', onStyleSelect);
3892 $('#sd_save_style').on('click', onSaveStyleClick);4159 $('#sd_save_style').on('click', onSaveStyleClick);
3893 $('#sd_delete_style').on('click', onDeleteStyleClick);4160 $('#sd_delete_style').on('click', onDeleteStyleClick);
public/scripts/extensions/stable-diffusion/settings.html+1 -14
@@ -27,11 +27,6 @@
27 <span data-i18n="sd_free_extend_txt">Extend free mode prompts</span>27 <span data-i18n="sd_free_extend_txt">Extend free mode prompts</span>
28 <small data-i18n="sd_free_extend_small">(interactive/commands)</small>28 <small data-i18n="sd_free_extend_small">(interactive/commands)</small>
29 </label>29 </label>
30 <label for="sd_expand" class="checkbox_label" data-i18n="[title]sd_expand" title="Automatically extend prompts using text generation model">
31 <input id="sd_expand" type="checkbox" />
32 <span data-i18n="sd_expand_txt">Auto-extend prompts</span>
33 <span class="right_menu_button fa-solid fa-triangle-exclamation" data-i18n="[title]sd_expand_warning" title="May produce unexpected results. Manual prompt editing is recommended."></span>
34 </label>
35 <label for="sd_snap" class="checkbox_label" data-i18n="[title]sd_snap" title="Snap generation requests with a forced aspect ratio (portraits, backgrounds) to the nearest known resolution, while trying to preserve the absolute pixel counts (recommended for SDXL).">30 <label for="sd_snap" class="checkbox_label" data-i18n="[title]sd_snap" title="Snap generation requests with a forced aspect ratio (portraits, backgrounds) to the nearest known resolution, while trying to preserve the absolute pixel counts (recommended for SDXL).">
36 <input id="sd_snap" type="checkbox" />31 <input id="sd_snap" type="checkbox" />
37 <span data-i18n="sd_snap_txt">Snap auto-adjusted resolutions</span>32 <span data-i18n="sd_snap_txt">Snap auto-adjusted resolutions</span>
@@ -308,7 +303,7 @@
308 </div>303 </div>
309304
310 <div class="flex-container">305 <div class="flex-container">
311 <div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="auto,vlad,drawthings">306 <div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="auto,vlad,drawthings,novel">
312 <small>307 <small>
313 <span data-i18n="Upscale by">Upscale by</span>308 <span data-i18n="Upscale by">Upscale by</span>
314 </small>309 </small>
@@ -332,14 +327,6 @@
332 <input class="neo-range-input" type="number" id="sd_hr_second_pass_steps_value" data-for="sd_hr_second_pass_steps" max="{{hr_second_pass_steps_max}}" step="{{hr_second_pass_steps_step}}" value="{{hr_second_pass_steps}}" >327 <input class="neo-range-input" type="number" id="sd_hr_second_pass_steps_value" data-for="sd_hr_second_pass_steps" max="{{hr_second_pass_steps_max}}" step="{{hr_second_pass_steps_step}}" value="{{hr_second_pass_steps}}" >
333 </div>328 </div>
334329
335 <div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="novel">
336 <small>
337 <span data-i18n="Upscale by">Upscale by</span>
338 </small>
339 <input class="neo-range-slider" type="range" id="sd_novel_upscale_ratio" name="sd_novel_upscale_ratio" min="{{novel_upscale_ratio_min}}" max="{{novel_upscale_ratio_max}}" step="{{novel_upscale_ratio_step}}" value="{{novel_upscale_ratio}}" >
340 <input class="neo-range-input" type="number" id="sd_novel_upscale_ratio_value" data-for="sd_novel_upscale_ratio" min="{{novel_upscale_ratio_min}}" max="{{novel_upscale_ratio_max}}" step="{{novel_upscale_ratio_step}}" value="{{novel_upscale_ratio}}" >
341 </div>
342
343 <div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="auto,vlad,comfy,horde,drawthings,extras">330 <div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="auto,vlad,comfy,horde,drawthings,extras">
344 <small>331 <small>
345 <span data-i18n="CLIP Skip">CLIP Skip</span>332 <span data-i18n="CLIP Skip">CLIP Skip</span>
public/scripts/openai.js+8 -2
@@ -724,6 +724,12 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul
724724
725 if (chatCompletion.canAfford(chatMessage)) {725 if (chatCompletion.canAfford(chatMessage)) {
726 if (type === 'continue' && oai_settings.continue_prefill && chatPrompt === firstNonInjected) {726 if (type === 'continue' && oai_settings.continue_prefill && chatPrompt === firstNonInjected) {
727 // in case we are using continue_prefill and the latest message is an assistant message, we want to prepend the users assistant prefill on the message
728 if (chatPrompt.role === 'assistant') {
729 const collection = new MessageCollection('continuePrefill', new Message(chatMessage.role, substituteParams(oai_settings.assistant_prefill + '\n\n') + chatMessage.content, chatMessage.identifier));
730 chatCompletion.add(collection, -1);
731 continue;
732 }
727 const collection = new MessageCollection('continuePrefill', chatMessage);733 const collection = new MessageCollection('continuePrefill', chatMessage);
728 chatCompletion.add(collection, -1);734 chatCompletion.add(collection, -1);
729 continue;735 continue;
@@ -1770,8 +1776,8 @@ async function sendOpenAIRequest(type, messages, signal) {
1770 generate_data['claude_use_sysprompt'] = oai_settings.claude_use_sysprompt;1776 generate_data['claude_use_sysprompt'] = oai_settings.claude_use_sysprompt;
1771 generate_data['stop'] = getCustomStoppingStrings(); // Claude shouldn't have limits on stop strings.1777 generate_data['stop'] = getCustomStoppingStrings(); // Claude shouldn't have limits on stop strings.
1772 generate_data['human_sysprompt_message'] = substituteParams(oai_settings.human_sysprompt_message);1778 generate_data['human_sysprompt_message'] = substituteParams(oai_settings.human_sysprompt_message);
1773 // Don't add a prefill on quiet gens (summarization)1779 // Don't add a prefill on quiet gens (summarization) and when using continue prefill.
1774 if (!isQuiet) {1780 if (!isQuiet && !(isContinue && oai_settings.continue_prefill)) {
1775 generate_data['assistant_prefill'] = isImpersonate ? substituteParams(oai_settings.assistant_impersonation) : substituteParams(oai_settings.assistant_prefill);1781 generate_data['assistant_prefill'] = isImpersonate ? substituteParams(oai_settings.assistant_impersonation) : substituteParams(oai_settings.assistant_prefill);
1776 }1782 }
1777 }1783 }
public/scripts/power-user.js+21 -0
@@ -290,6 +290,7 @@ let power_user = {
290 restore_user_input: true,290 restore_user_input: true,
291 reduced_motion: false,291 reduced_motion: false,
292 compact_input_area: true,292 compact_input_area: true,
293 show_swipe_num_all_messages: false,
293 auto_connect: false,294 auto_connect: false,
294 auto_load_chat: false,295 auto_load_chat: false,
295 forbid_external_media: true,296 forbid_external_media: true,
@@ -469,6 +470,11 @@ function switchCompactInputArea() {
469 $('#compact_input_area').prop('checked', power_user.compact_input_area);470 $('#compact_input_area').prop('checked', power_user.compact_input_area);
470}471}
471472
473export function switchSwipeNumAllMessages() {
474 $('#show_swipe_num_all_messages').prop('checked', power_user.show_swipe_num_all_messages);
475 $('.mes:not(.last_mes) .swipes-counter').css('opacity', '').toggle(power_user.show_swipe_num_all_messages);
476}
477
472var originalSliderValues = [];478var originalSliderValues = [];
473479
474async function switchLabMode() {480async function switchLabMode() {
@@ -1283,6 +1289,13 @@ function applyTheme(name) {
1283 switchCompactInputArea();1289 switchCompactInputArea();
1284 },1290 },
1285 },1291 },
1292 {
1293 key: 'show_swipe_num_all_messages',
1294 action: () => {
1295 $('#show_swipe_num_all_messages').prop('checked', power_user.show_swipe_num_all_messages);
1296 switchSwipeNumAllMessages();
1297 },
1298 },
1286 ];1299 ];
12871300
1288 for (const { key, selector, type, action } of themeProperties) {1301 for (const { key, selector, type, action } of themeProperties) {
@@ -1352,6 +1365,7 @@ function applyPowerUserSettings() {
1352 switchHideChatAvatars();1365 switchHideChatAvatars();
1353 switchTokenCount();1366 switchTokenCount();
1354 switchMessageActions();1367 switchMessageActions();
1368 switchSwipeNumAllMessages();
1355}1369}
13561370
1357function getExampleMessagesBehavior() {1371function getExampleMessagesBehavior() {
@@ -2296,6 +2310,7 @@ function getThemeObject(name) {
2296 zoomed_avatar_magnification: power_user.zoomed_avatar_magnification,2310 zoomed_avatar_magnification: power_user.zoomed_avatar_magnification,
2297 reduced_motion: power_user.reduced_motion,2311 reduced_motion: power_user.reduced_motion,
2298 compact_input_area: power_user.compact_input_area,2312 compact_input_area: power_user.compact_input_area,
2313 show_swipe_num_all_messages: power_user.show_swipe_num_all_messages,
2299 };2314 };
2300}2315}
23012316
@@ -3755,6 +3770,12 @@ $(document).ready(() => {
3755 saveSettingsDebounced();3770 saveSettingsDebounced();
3756 });3771 });
37573772
3773 $('#show_swipe_num_all_messages').on('input', function () {
3774 power_user.show_swipe_num_all_messages = !!$(this).prop('checked');
3775 switchSwipeNumAllMessages();
3776 saveSettingsDebounced();
3777 });
3778
3758 $('#auto-connect-checkbox').on('input', function () {3779 $('#auto-connect-checkbox').on('input', function () {
3759 power_user.auto_connect = !!$(this).prop('checked');3780 power_user.auto_connect = !!$(this).prop('checked');
3760 saveSettingsDebounced();3781 saveSettingsDebounced();
public/scripts/slash-commands.js+110 -55
@@ -70,6 +70,7 @@ import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
70import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';70import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
71import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js';71import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js';
72import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js';72import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js';
73import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';
73export {74export {
74 executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,75 executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,
75};76};
@@ -242,6 +243,7 @@ export function initDefaultSlashCommands() {
242 SlashCommandParser.addCommandObject(SlashCommand.fromProps({243 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
243 name: 'sendas',244 name: 'sendas',
244 callback: sendMessageAs,245 callback: sendMessageAs,
246 returns: 'Optionally the text of the sent message, if specified in the "return" argument',
245 namedArgumentList: [247 namedArgumentList: [
246 SlashCommandNamedArgument.fromProps({248 SlashCommandNamedArgument.fromProps({
247 name: 'name',249 name: 'name',
@@ -269,6 +271,14 @@ export function initDefaultSlashCommands() {
269 typeList: [ARGUMENT_TYPE.NUMBER],271 typeList: [ARGUMENT_TYPE.NUMBER],
270 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),272 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
271 }),273 }),
274 SlashCommandNamedArgument.fromProps({
275 name: 'return',
276 description: 'The way how you want the return value to be provided',
277 typeList: [ARGUMENT_TYPE.STRING],
278 defaultValue: 'none',
279 enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
280 forceEnum: true,
281 }),
272 ],282 ],
273 unnamedArgumentList: [283 unnamedArgumentList: [
274 new SlashCommandArgument(284 new SlashCommandArgument(
@@ -301,6 +311,7 @@ export function initDefaultSlashCommands() {
301 name: 'sys',311 name: 'sys',
302 callback: sendNarratorMessage,312 callback: sendNarratorMessage,
303 aliases: ['nar'],313 aliases: ['nar'],
314 returns: 'Optionally the text of the sent message, if specified in the "return" argument',
304 namedArgumentList: [315 namedArgumentList: [
305 new SlashCommandNamedArgument(316 new SlashCommandNamedArgument(
306 'compact',317 'compact',
@@ -316,6 +327,14 @@ export function initDefaultSlashCommands() {
316 typeList: [ARGUMENT_TYPE.NUMBER],327 typeList: [ARGUMENT_TYPE.NUMBER],
317 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),328 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
318 }),329 }),
330 SlashCommandNamedArgument.fromProps({
331 name: 'return',
332 description: 'The way how you want the return value to be provided',
333 typeList: [ARGUMENT_TYPE.STRING],
334 defaultValue: 'none',
335 enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
336 forceEnum: true,
337 }),
319 ],338 ],
320 unnamedArgumentList: [339 unnamedArgumentList: [
321 new SlashCommandArgument(340 new SlashCommandArgument(
@@ -355,6 +374,7 @@ export function initDefaultSlashCommands() {
355 SlashCommandParser.addCommandObject(SlashCommand.fromProps({374 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
356 name: 'comment',375 name: 'comment',
357 callback: sendCommentMessage,376 callback: sendCommentMessage,
377 returns: 'Optionally the text of the sent message, if specified in the "return" argument',
358 namedArgumentList: [378 namedArgumentList: [
359 new SlashCommandNamedArgument(379 new SlashCommandNamedArgument(
360 'compact',380 'compact',
@@ -370,6 +390,14 @@ export function initDefaultSlashCommands() {
370 typeList: [ARGUMENT_TYPE.NUMBER],390 typeList: [ARGUMENT_TYPE.NUMBER],
371 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),391 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
372 }),392 }),
393 SlashCommandNamedArgument.fromProps({
394 name: 'return',
395 description: 'The way how you want the return value to be provided',
396 typeList: [ARGUMENT_TYPE.STRING],
397 defaultValue: 'none',
398 enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
399 forceEnum: true,
400 }),
373 ],401 ],
374 unnamedArgumentList: [402 unnamedArgumentList: [
375 new SlashCommandArgument(403 new SlashCommandArgument(
@@ -509,7 +537,7 @@ export function initDefaultSlashCommands() {
509 SlashCommandParser.addCommandObject(SlashCommand.fromProps({537 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
510 name: 'ask',538 name: 'ask',
511 callback: askCharacter,539 callback: askCharacter,
512 returns: 'the generated text',540 returns: 'Optionally the text of the sent message, if specified in the "return" argument',
513 namedArgumentList: [541 namedArgumentList: [
514 SlashCommandNamedArgument.fromProps({542 SlashCommandNamedArgument.fromProps({
515 name: 'name',543 name: 'name',
@@ -518,6 +546,14 @@ export function initDefaultSlashCommands() {
518 isRequired: true,546 isRequired: true,
519 enumProvider: commonEnumProviders.characters('character'),547 enumProvider: commonEnumProviders.characters('character'),
520 }),548 }),
549 SlashCommandNamedArgument.fromProps({
550 name: 'return',
551 description: 'The way how you want the return value to be provided',
552 typeList: [ARGUMENT_TYPE.STRING],
553 defaultValue: 'pipe',
554 enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
555 forceEnum: true,
556 }),
521 ],557 ],
522 unnamedArgumentList: [558 unnamedArgumentList: [
523 new SlashCommandArgument(559 new SlashCommandArgument(
@@ -556,6 +592,7 @@ export function initDefaultSlashCommands() {
556 SlashCommandParser.addCommandObject(SlashCommand.fromProps({592 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
557 name: 'send',593 name: 'send',
558 callback: sendUserMessageCallback,594 callback: sendUserMessageCallback,
595 returns: 'Optionally the text of the sent message, if specified in the "return" argument',
559 namedArgumentList: [596 namedArgumentList: [
560 new SlashCommandNamedArgument(597 new SlashCommandNamedArgument(
561 'compact',598 'compact',
@@ -578,6 +615,14 @@ export function initDefaultSlashCommands() {
578 defaultValue: '{{user}}',615 defaultValue: '{{user}}',
579 enumProvider: commonEnumProviders.personas,616 enumProvider: commonEnumProviders.personas,
580 }),617 }),
618 SlashCommandNamedArgument.fromProps({
619 name: 'return',
620 description: 'The way how you want the return value to be provided',
621 typeList: [ARGUMENT_TYPE.STRING],
622 defaultValue: 'none',
623 enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
624 forceEnum: true,
625 }),
581 ],626 ],
582 unnamedArgumentList: [627 unnamedArgumentList: [
583 new SlashCommandArgument(628 new SlashCommandArgument(
@@ -1568,12 +1613,21 @@ export function initDefaultSlashCommands() {
1568 SlashCommandParser.addCommandObject(SlashCommand.fromProps({1613 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1569 name: 'listinjects',1614 name: 'listinjects',
1570 callback: listInjectsCallback,1615 callback: listInjectsCallback,
1571 helpString: 'Lists all script injections for the current chat. Displays injects in a popup by default. Use the <code>format</code> argument to change the output format.',1616 helpString: 'Lists all script injections for the current chat. Displays injects in a popup by default. Use the <code>return</code> argument to change the return type.',
1572 returns: 'JSON object of script injections',1617 returns: 'Optionalls the JSON object of script injections',
1573 namedArgumentList: [1618 namedArgumentList: [
1574 SlashCommandNamedArgument.fromProps({1619 SlashCommandNamedArgument.fromProps({
1620 name: 'return',
1621 description: 'The way how you want the return value to be provided',
1622 typeList: [ARGUMENT_TYPE.STRING],
1623 defaultValue: 'popup-html',
1624 enumList: slashCommandReturnHelper.enumList({ allowPipe: false, allowObject: true, allowChat: true, allowPopup: true, allowTextVersion: false }),
1625 forceEnum: true,
1626 }),
1627 // TODO remove some day
1628 SlashCommandNamedArgument.fromProps({
1575 name: 'format',1629 name: 'format',
1576 description: 'output format',1630 description: '!!! DEPRECATED - use "return" instead !!! output format',
1577 typeList: [ARGUMENT_TYPE.STRING],1631 typeList: [ARGUMENT_TYPE.STRING],
1578 isRequired: true,1632 isRequired: true,
1579 forceEnum: true,1633 forceEnum: true,
@@ -1842,37 +1896,43 @@ function injectCallback(args, value) {
1842}1896}
18431897
1844async function listInjectsCallback(args) {1898async function listInjectsCallback(args) {
1845 const type = String(args?.format).toLowerCase().trim();1899 /** @type {import('./slash-commands/SlashCommandReturnHelper.js').SlashCommandReturnType} */
1846 if (!chat_metadata.script_injects || !Object.keys(chat_metadata.script_injects).length) {1900 let returnType = args.return;
1847 type !== 'none' && toastr.info('No script injections for the current chat');1901
1848 return JSON.stringify({});1902 // Old legacy return type handling
1849 }1903 if (args.format) {
18501904 toastr.warning(`Legacy argument 'format' with value '${args.format}' is deprecated. Please use 'return' instead. Routing to the correct return type...`, 'Deprecation warning');
1851 const injects = Object.entries(chat_metadata.script_injects)1905 const type = String(args?.format).toLowerCase().trim();
1852 .map(([id, inject]) => {1906 if (!chat_metadata.script_injects || !Object.keys(chat_metadata.script_injects).length) {
1853 const position = Object.entries(extension_prompt_types);1907 type !== 'none' && toastr.info('No script injections for the current chat');
1854 const positionName = position.find(([_, value]) => value === inject.position)?.[0] ?? 'unknown';1908 }
1855 return `* **${id}**: <code>${inject.value}</code> (${positionName}, depth: ${inject.depth}, scan: ${inject.scan ?? false}, role: ${inject.role ?? extension_prompt_roles.SYSTEM})`;1909 switch (type) {
1856 })1910 case 'none':
1857 .join('\n');1911 returnType = 'none';
18581912 break;
1859 const converter = new showdown.Converter();1913 case 'chat':
1860 const messageText = `### Script injections:\n${injects}`;1914 returnType = 'chat-html';
1861 const htmlMessage = DOMPurify.sanitize(converter.makeHtml(messageText));1915 break;
18621916 case 'popup':
1863 switch (type) {1917 default:
1864 case 'none':1918 returnType = 'popup-html';
1865 break;1919 break;
1866 case 'chat':1920 }
1867 sendSystemMessage(system_message_types.GENERIC, htmlMessage);
1868 break;
1869 case 'popup':
1870 default:
1871 await callGenericPopup(htmlMessage, POPUP_TYPE.TEXT);
1872 break;
1873 }1921 }
18741922
1875 return JSON.stringify(chat_metadata.script_injects);1923 // Now the actual new return type handling
1924 const buildTextValue = (injects) => {
1925 const injectsStr = Object.entries(injects)
1926 .map(([id, inject]) => {
1927 const position = Object.entries(extension_prompt_types);
1928 const positionName = position.find(([_, value]) => value === inject.position)?.[0] ?? 'unknown';
1929 return `* **${id}**: <code>${inject.value}</code> (${positionName}, depth: ${inject.depth}, scan: ${inject.scan ?? false}, role: ${inject.role ?? extension_prompt_roles.SYSTEM})`;
1930 })
1931 .join('\n');
1932 return `### Script injections:\n${injectsStr || 'No script injections for the current chat'}`;
1933 };
1934
1935 return await slashCommandReturnHelper.doReturn(returnType ?? 'popup-html', chat_metadata.script_injects ?? {}, { objectToStringFunc: buildTextValue });
1876}1936}
18771937
1878/**1938/**
@@ -2559,7 +2619,7 @@ async function askCharacter(args, text) {
2559 // Not supported in group chats2619 // Not supported in group chats
2560 // TODO: Maybe support group chats?2620 // TODO: Maybe support group chats?
2561 if (selected_group) {2621 if (selected_group) {
2562 toastr.error('Cannot run /ask command in a group chat!');2622 toastr.warning('Cannot run /ask command in a group chat!');
2563 return '';2623 return '';
2564 }2624 }
25652625
@@ -2633,7 +2693,9 @@ async function askCharacter(args, text) {
2633 }2693 }
2634 }2694 }
26352695
2636 return askResult;2696 const message = askResult ? chat[chat.length - 1] : null;
2697
2698 return await slashCommandReturnHelper.doReturn(args.return ?? 'pipe', message, { objectToStringFunc: x => x.mes });
2637}2699}
26382700
2639async function hideMessageCallback(_, arg) {2701async function hideMessageCallback(_, arg) {
@@ -2908,7 +2970,7 @@ function findPersonaByName(name) {
29082970
2909async function sendUserMessageCallback(args, text) {2971async function sendUserMessageCallback(args, text) {
2910 if (!text) {2972 if (!text) {
2911 console.warn('WARN: No text provided for /send command');2973 toastr.warning('You must specify text to send');
2912 return;2974 return;
2913 }2975 }
29142976
@@ -2924,16 +2986,17 @@ async function sendUserMessageCallback(args, text) {
2924 insertAt = chat.length + insertAt;2986 insertAt = chat.length + insertAt;
2925 }2987 }
29262988
2989 let message;
2927 if ('name' in args) {2990 if ('name' in args) {
2928 const name = args.name || '';2991 const name = args.name || '';
2929 const avatar = findPersonaByName(name) || user_avatar;2992 const avatar = findPersonaByName(name) || user_avatar;
2930 await sendMessageAsUser(text, bias, insertAt, compact, name, avatar);2993 message = await sendMessageAsUser(text, bias, insertAt, compact, name, avatar);
2931 }2994 }
2932 else {2995 else {
2933 await sendMessageAsUser(text, bias, insertAt, compact);2996 message = await sendMessageAsUser(text, bias, insertAt, compact);
2934 }2997 }
29352998
2936 return '';2999 return await slashCommandReturnHelper.doReturn(args.return ?? 'none', message, { objectToStringFunc: x => x.mes });
2937}3000}
29383001
2939async function deleteMessagesByNameCallback(_, name) {3002async function deleteMessagesByNameCallback(_, name) {
@@ -3221,30 +3284,20 @@ export function getNameAndAvatarForMessage(character, name = null) {
32213284
3222export async function sendMessageAs(args, text) {3285export async function sendMessageAs(args, text) {
3223 if (!text) {3286 if (!text) {
3287 toastr.warning('You must specify text to send as');
3224 return '';3288 return '';
3225 }3289 }
32263290
3227 let name;3291 let name = args.name?.trim();
3228 let mesText;3292 let mesText;
32293293
3230 if (args.name) {3294 if (!name) {
3231 name = args.name.trim();
3232
3233 if (!name && !text) {
3234 toastr.warning('You must specify a name and text to send as');
3235 return '';
3236 }
3237 } else {
3238 const namelessWarningKey = 'sendAsNamelessWarningShown';3295 const namelessWarningKey = 'sendAsNamelessWarningShown';
3239 if (localStorage.getItem(namelessWarningKey) !== 'true') {3296 if (localStorage.getItem(namelessWarningKey) !== 'true') {
3240 toastr.warning('To avoid confusion, please use /sendas name="Character Name"', 'Name defaulted to {{char}}', { timeOut: 10000 });3297 toastr.warning('To avoid confusion, please use /sendas name="Character Name"', 'Name defaulted to {{char}}', { timeOut: 10000 });
3241 localStorage.setItem(namelessWarningKey, 'true');3298 localStorage.setItem(namelessWarningKey, 'true');
3242 }3299 }
3243 name = name2;3300 name = name2;
3244 if (!text) {
3245 toastr.warning('You must specify text to send as');
3246 return '';
3247 }
3248 }3301 }
32493302
3250 mesText = text.trim();3303 mesText = text.trim();
@@ -3321,11 +3374,12 @@ export async function sendMessageAs(args, text) {
3321 await saveChatConditional();3374 await saveChatConditional();
3322 }3375 }
33233376
3324 return '';3377 return await slashCommandReturnHelper.doReturn(args.return ?? 'none', message, { objectToStringFunc: x => x.mes });
3325}3378}
33263379
3327export async function sendNarratorMessage(args, text) {3380export async function sendNarratorMessage(args, text) {
3328 if (!text) {3381 if (!text) {
3382 toastr.warning('You must specify text to send');
3329 return '';3383 return '';
3330 }3384 }
33313385
@@ -3374,7 +3428,7 @@ export async function sendNarratorMessage(args, text) {
3374 await saveChatConditional();3428 await saveChatConditional();
3375 }3429 }
33763430
3377 return '';3431 return await slashCommandReturnHelper.doReturn(args.return ?? 'none', message, { objectToStringFunc: x => x.mes });
3378}3432}
33793433
3380export async function promptQuietForLoudResponse(who, text) {3434export async function promptQuietForLoudResponse(who, text) {
@@ -3420,6 +3474,7 @@ export async function promptQuietForLoudResponse(who, text) {
34203474
3421async function sendCommentMessage(args, text) {3475async function sendCommentMessage(args, text) {
3422 if (!text) {3476 if (!text) {
3477 toastr.warning('You must specify text to send');
3423 return '';3478 return '';
3424 }3479 }
34253480
@@ -3462,7 +3517,7 @@ async function sendCommentMessage(args, text) {
3462 await saveChatConditional();3517 await saveChatConditional();
3463 }3518 }
34643519
3465 return '';3520 return await slashCommandReturnHelper.doReturn(args.return ?? 'none', message, { objectToStringFunc: x => x.mes });
3466}3521}
34673522
3468/**3523/**
public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js+1 -0
@@ -36,6 +36,7 @@ export const enumIcons = {
36 message: '💬',36 message: '💬',
37 voice: '🎤',37 voice: '🎤',
38 server: '🖥️',38 server: '🖥️',
39 popup: '🗔',
3940
40 true: '✔️',41 true: '✔️',
41 false: '❌',42 false: '❌',
public/scripts/slash-commands/SlashCommandReturnHelper.js+80 -0
@@ -0,0 +1,80 @@
1import { sendSystemMessage, system_message_types } from '../../script.js';
2import { callGenericPopup, POPUP_TYPE } from '../popup.js';
3import { escapeHtml } from '../utils.js';
4import { enumIcons } from './SlashCommandCommonEnumsProvider.js';
5import { enumTypes, SlashCommandEnumValue } from './SlashCommandEnumValue.js';
6
7/** @typedef {'pipe'|'object'|'chat-html'|'chat-text'|'popup-html'|'popup-text'|'toast-html'|'toast-text'|'console'|'none'} SlashCommandReturnType */
8
9export const slashCommandReturnHelper = {
10 // Without this, VSCode formatter fucks up JS docs. Don't ask me why.
11 _: false,
12
13 /**
14 * Gets/creates the enum list of types of return relevant for a slash command
15 *
16 * @param {object} [options={}] Options
17 * @param {boolean} [options.allowPipe=true] Allow option to pipe the return value
18 * @param {boolean} [options.allowObject=false] Allow option to return the value as an object
19 * @param {boolean} [options.allowChat=false] Allow option to return the value as a chat message
20 * @param {boolean} [options.allowPopup=false] Allow option to return the value as a popup
21 * @param {boolean}[options.allowTextVersion=true] Used in combination with chat/popup/toast, some of them do not make sense for text versions, e.g.if you are building a HTML string anyway
22 * @returns {SlashCommandEnumValue[]} The enum list
23 */
24 enumList: ({ allowPipe = true, allowObject = false, allowChat = false, allowPopup = false, allowTextVersion = true } = {}) => [
25 allowPipe && new SlashCommandEnumValue('pipe', 'Return to the pipe for the next command', enumTypes.name, '|'),
26 allowObject && new SlashCommandEnumValue('object', 'Return as an object (or array) to the pipe for the next command', enumTypes.variable, enumIcons.dictionary),
27 allowChat && new SlashCommandEnumValue('chat-html', 'Sending a chat message with the return value - Can display HTML', enumTypes.command, enumIcons.message),
28 allowChat && allowTextVersion && new SlashCommandEnumValue('chat-text', 'Sending a chat message with the return value - Will only display as text', enumTypes.qr, enumIcons.message),
29 allowPopup && new SlashCommandEnumValue('popup-html', 'Showing as a popup with the return value - Can display HTML', enumTypes.command, enumIcons.popup),
30 allowPopup && allowTextVersion && new SlashCommandEnumValue('popup-text', 'Showing as a popup with the return value - Will only display as text', enumTypes.qr, enumIcons.popup),
31 new SlashCommandEnumValue('toast-html', 'Show the return value as a toast notification - Can display HTML', enumTypes.command, 'ℹ️'),
32 allowTextVersion && new SlashCommandEnumValue('toast-text', 'Show the return value as a toast notification - Will only display as text', enumTypes.qr, 'ℹ️'),
33 new SlashCommandEnumValue('console', 'Log the return value (object, if it can be one) to the console', enumTypes.enum, '>'),
34 new SlashCommandEnumValue('none', 'No return value'),
35 ].filter(x => !!x),
36
37 /**
38 * Handles the return value based on the specified type
39 *
40 * @param {SlashCommandReturnType} type The type of return
41 * @param {object|number|string} value The value to return
42 * @param {object} [options={}] Options
43 * @param {(o: object) => string} [options.objectToStringFunc=null] Function to convert the object to a string, if object was provided and 'object' was not the chosen return type
44 * @param {(o: object) => string} [options.objectToHtmlFunc=null] Analog to 'objectToStringFunc', which will be used here if not provided - but can do a different string layout if HTML is requested
45 * @returns {Promise<*>} The processed return value
46 */
47 async doReturn(type, value, { objectToStringFunc = o => o?.toString(), objectToHtmlFunc = null } = {}) {
48 const shouldHtml = type.endsWith('html');
49 const actualConverterFunc = shouldHtml && objectToHtmlFunc ? objectToHtmlFunc : objectToStringFunc;
50 const stringValue = typeof value !== 'string' ? actualConverterFunc(value) : value;
51
52 switch (type) {
53 case 'popup-html':
54 case 'popup-text':
55 case 'chat-text':
56 case 'chat-html':
57 case 'toast-text':
58 case 'toast-html': {
59 const htmlOrNotHtml = shouldHtml ? DOMPurify.sanitize((new showdown.Converter()).makeHtml(stringValue)) : escapeHtml(stringValue);
60
61 if (type.startsWith('popup')) await callGenericPopup(htmlOrNotHtml, POPUP_TYPE.TEXT);
62 if (type.startsWith('chat')) sendSystemMessage(system_message_types.GENERIC, htmlOrNotHtml);
63 if (type.startsWith('toast')) toastr.info(htmlOrNotHtml, null, { escapeHtml: !shouldHtml });
64
65 return '';
66 }
67 case 'pipe':
68 return stringValue ?? '';
69 case 'object':
70 return JSON.stringify(value);
71 case 'console':
72 console.info(value);
73 return '';
74 case 'none':
75 return '';
76 default:
77 throw new Error(`Unknown return type: ${type}`);
78 }
79 },
80};
public/scripts/variables.js+47 -27
@@ -11,6 +11,7 @@ import { SlashCommandClosureResult } from './slash-commands/SlashCommandClosureR
11import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';11import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
12import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';12import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
13import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';13import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
14import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';
14import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';15import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
15import { isFalseBoolean, convertValueType, isTrueBoolean } from './utils.js';16import { isFalseBoolean, convertValueType, isTrueBoolean } from './utils.js';
1617
@@ -305,7 +306,28 @@ export function replaceVariableMacros(input) {
305}306}
306307
307async function listVariablesCallback(args) {308async function listVariablesCallback(args) {
308 const type = String(args?.format || '').toLowerCase().trim() || 'popup';309 /** @type {import('./slash-commands/SlashCommandReturnHelper.js').SlashCommandReturnType} */
310 let returnType = args.return;
311
312 // Old legacy return type handling
313 if (args.format) {
314 toastr.warning(`Legacy argument 'format' with value '${args.format}' is deprecated. Please use 'return' instead. Routing to the correct return type...`, 'Deprecation warning');
315 const type = String(args?.format).toLowerCase().trim();
316 switch (type) {
317 case 'none':
318 returnType = 'none';
319 break;
320 case 'chat':
321 returnType = 'chat-html';
322 break;
323 case 'popup':
324 default:
325 returnType = 'popup-html';
326 break;
327 }
328 }
329
330 // Now the actual new return type handling
309 const scope = String(args?.scope || '').toLowerCase().trim() || 'all';331 const scope = String(args?.scope || '').toLowerCase().trim() || 'all';
310 if (!chat_metadata.variables) {332 if (!chat_metadata.variables) {
311 chat_metadata.variables = {};333 chat_metadata.variables = {};
@@ -317,35 +339,24 @@ async function listVariablesCallback(args) {
317 const localVariables = includeLocalVariables ? Object.entries(chat_metadata.variables).map(([name, value]) => `${name}: ${value}`) : [];339 const localVariables = includeLocalVariables ? Object.entries(chat_metadata.variables).map(([name, value]) => `${name}: ${value}`) : [];
318 const globalVariables = includeGlobalVariables ? Object.entries(extension_settings.variables.global).map(([name, value]) => `${name}: ${value}`) : [];340 const globalVariables = includeGlobalVariables ? Object.entries(extension_settings.variables.global).map(([name, value]) => `${name}: ${value}`) : [];
319341
342 const buildTextValue = (_) => {
343 const localVariablesString = localVariables.length > 0 ? localVariables.join('\n\n') : 'No local variables';
344 const globalVariablesString = globalVariables.length > 0 ? globalVariables.join('\n\n') : 'No global variables';
345 const chatName = getCurrentChatId();
346
347 const message = [
348 includeLocalVariables ? `### Local variables (${chatName}):\n${localVariablesString}` : '',
349 includeGlobalVariables ? `### Global variables:\n${globalVariablesString}` : '',
350 ].filter(x => x).join('\n\n');
351 return message;
352 };
353
320 const jsonVariables = [354 const jsonVariables = [
321 ...Object.entries(chat_metadata.variables).map(x => ({ key: x[0], value: x[1], scope: 'local' })),355 ...Object.entries(chat_metadata.variables).map(x => ({ key: x[0], value: x[1], scope: 'local' })),
322 ...Object.entries(extension_settings.variables.global).map(x => ({ key: x[0], value: x[1], scope: 'global' })),356 ...Object.entries(extension_settings.variables.global).map(x => ({ key: x[0], value: x[1], scope: 'global' })),
323 ];357 ];
324358
325 const localVariablesString = localVariables.length > 0 ? localVariables.join('\n\n') : 'No local variables';359 return await slashCommandReturnHelper.doReturn(returnType ?? 'popup-html', jsonVariables, { objectToStringFunc: buildTextValue });
326 const globalVariablesString = globalVariables.length > 0 ? globalVariables.join('\n\n') : 'No global variables';
327 const chatName = getCurrentChatId();
328
329 const converter = new showdown.Converter();
330 const message = [
331 includeLocalVariables ? `### Local variables (${chatName}):\n${localVariablesString}` : '',
332 includeGlobalVariables ? `### Global variables:\n${globalVariablesString}` : '',
333 ].filter(x => x).join('\n\n');
334 const htmlMessage = DOMPurify.sanitize(converter.makeHtml(message));
335
336 switch (type) {
337 case 'none':
338 break;
339 case 'chat':
340 sendSystemMessage(system_message_types.GENERIC, htmlMessage);
341 break;
342 case 'popup':
343 default:
344 await callGenericPopup(htmlMessage, POPUP_TYPE.TEXT);
345 break;
346 }
347
348 return JSON.stringify(jsonVariables);
349}360}
350361
351/**362/**
@@ -916,7 +927,7 @@ export function registerVariableCommands() {
916 name: 'listvar',927 name: 'listvar',
917 callback: listVariablesCallback,928 callback: listVariablesCallback,
918 aliases: ['listchatvar'],929 aliases: ['listchatvar'],
919 helpString: 'List registered chat variables. Displays variables in a popup by default. Use the <code>format</code> argument to change the output format.',930 helpString: 'List registered chat variables. Displays variables in a popup by default. Use the <code>return</code> argument to change the return type.',
920 returns: 'JSON list of local variables',931 returns: 'JSON list of local variables',
921 namedArgumentList: [932 namedArgumentList: [
922 SlashCommandNamedArgument.fromProps({933 SlashCommandNamedArgument.fromProps({
@@ -933,8 +944,17 @@ export function registerVariableCommands() {
933 ],944 ],
934 }),945 }),
935 SlashCommandNamedArgument.fromProps({946 SlashCommandNamedArgument.fromProps({
947 name: 'return',
948 description: 'The way how you want the return value to be provided',
949 typeList: [ARGUMENT_TYPE.STRING],
950 defaultValue: 'popup-html',
951 enumList: slashCommandReturnHelper.enumList({ allowPipe: false, allowObject: true, allowChat: true, allowPopup: true, allowTextVersion: false }),
952 forceEnum: true,
953 }),
954 // TODO remove some day
955 SlashCommandNamedArgument.fromProps({
936 name: 'format',956 name: 'format',
937 description: 'output format',957 description: '!!! DEPRECATED - use "return" instead !!! output format',
938 typeList: [ARGUMENT_TYPE.STRING],958 typeList: [ARGUMENT_TYPE.STRING],
939 isRequired: true,959 isRequired: true,
940 forceEnum: true,960 forceEnum: true,
public/style.css+27 -8
@@ -969,34 +969,53 @@ body .panelControlBar {
969969
970/* SWIPE RELATED STYLES*/970/* SWIPE RELATED STYLES*/
971971
972.mes {
973 --swipeCounterHeight: 15px;
974 --swipeCounterMargin: 5px;
975}
976
972.swipe_right,977.swipe_right,
973.swipe_left {978.swipe_left {
974 height: 40px;979 width: 25px;
975 width: 40px;980 height: 25px;
976 opacity: 0.3;981 opacity: 0.3;
977 align-items: center;982 align-items: center;
978 justify-content: center;983 justify-content: center;
979 z-index: 9999;984 z-index: 9999;
980 grid-row-start: 2;985 grid-row-start: 2;
981 font-size: 30px;986 font-size: 20px;
982 cursor: pointer;987 cursor: pointer;
983 align-self: center;988 align-self: center;
989}
990
991.swipe_left {
984 position: absolute;992 position: absolute;
985bottom: 15px;993 bottom: calc(var(--swipeCounterHeight) + var(--swipeCounterMargin));
986flex-flow: column;994 flex-flow: column;
987}995}
988996
997.swipeRightBlock {
998 position: absolute;
999 right: 0;
1000 bottom: 0;
1001}
9891002
990.swipes-counter {1003.swipes-counter {
991 color: var(--SmartThemeBodyColor);1004 color: var(--SmartThemeBodyColor);
992 font-size: 12px;1005 font-size: 12px;
993 padding: 0;1006 padding: 0 5px;
994 font-family: var(--mainFontFamily);1007 font-family: var(--mainFontFamily);
995 font-weight: 400;1008 font-weight: 400;
996 align-self: center;1009 align-self: center;
997 min-width: 40px;1010 min-width: 40px;
998 display: flex;1011 display: flex;
999 justify-content: center;1012 justify-content: center;
1013 margin-bottom: var(--swipeCounterMargin);
1014 height: var(--swipeCounterHeight);
1015}
1016
1017.mes:not(.last_mes) .swipes-counter {
1018 opacity: 0.3;
1000}1019}
10011020
1002.swipe_left {1021.swipe_left {
@@ -1006,7 +1025,7 @@ flex-flow: column;
10061025
1007.swipe_right {1026.swipe_right {
1008 right: 5px;1027 right: 5px;
1009 align-self:end;1028 align-self: center;
1010}1029}
10111030
1012.ui-settings {1031.ui-settings {
@@ -2640,7 +2659,7 @@ select option:not(:checked) {
26402659
2641#instruct_enabled_label .menu_button:not(.toggleEnabled),2660#instruct_enabled_label .menu_button:not(.toggleEnabled),
2642#sysprompt_enabled_label .menu_button:not(.toggleEnabled) {2661#sysprompt_enabled_label .menu_button:not(.toggleEnabled) {
2643 color: Red;2662 color: Red;
2644}2663}
26452664
2646.displayBlock {2665.displayBlock {
src/endpoints/stable-diffusion.js+0 -69
@@ -10,41 +10,6 @@ const { readSecret, SECRET_KEYS } = require('./secrets.js');
10const FormData = require('form-data');10const FormData = require('form-data');
1111
12/**12/**
13 * Sanitizes a string.
14 * @param {string} x String to sanitize
15 * @returns {string} Sanitized string
16 */
17function safeStr(x) {
18 x = String(x);
19 x = x.replace(/ +/g, ' ');
20 x = x.trim();
21 x = x.replace(/^[\s,.]+|[\s,.]+$/g, '');
22 return x;
23}
24
25const splitStrings = [
26 ', extremely',
27 ', intricate,',
28];
29
30const dangerousPatterns = '[]【】()()|::';
31
32/**
33 * Removes patterns from a string.
34 * @param {string} x String to sanitize
35 * @param {string} pattern Pattern to remove
36 * @returns {string} Sanitized string
37 */
38function removePattern(x, pattern) {
39 for (let i = 0; i < pattern.length; i++) {
40 let p = pattern[i];
41 let regex = new RegExp('\\' + p, 'g');
42 x = x.replace(regex, '');
43 }
44 return x;
45}
46
47/**
48 * Gets the comfy workflows.13 * Gets the comfy workflows.
49 * @param {import('../users.js').UserDirectoryList} directories14 * @param {import('../users.js').UserDirectoryList} directories
50 * @returns {string[]} List of comfy workflows15 * @returns {string[]} List of comfy workflows
@@ -391,40 +356,6 @@ router.post('/sd-next/upscalers', jsonParser, async (request, response) => {
391 }356 }
392});357});
393358
394/**
395 * SD prompt expansion using GPT-2 text generation model.
396 * Adapted from: https://github.com/lllyasviel/Fooocus/blob/main/modules/expansion.py
397 */
398router.post('/expand', jsonParser, async (request, response) => {
399 const originalPrompt = request.body.prompt;
400
401 if (!originalPrompt) {
402 console.warn('No prompt provided for SD expansion.');
403 return response.send({ prompt: '' });
404 }
405
406 console.log('Refine prompt input:', originalPrompt);
407 const splitString = splitStrings[Math.floor(Math.random() * splitStrings.length)];
408 let prompt = safeStr(originalPrompt) + splitString;
409
410 try {
411 const task = 'text-generation';
412 const module = await import('../transformers.mjs');
413 const pipe = await module.default.getPipeline(task);
414
415 const result = await pipe(prompt, { num_beams: 1, max_new_tokens: 256, do_sample: true });
416
417 const newText = result[0].generated_text;
418 const newPrompt = safeStr(removePattern(newText, dangerousPatterns));
419 console.log('Refine prompt output:', newPrompt);
420
421 return response.send({ prompt: newPrompt });
422 } catch {
423 console.warn('Failed to load transformers.js pipeline.');
424 return response.send({ prompt: originalPrompt });
425 }
426});
427
428const comfy = express.Router();359const comfy = express.Router();
429360
430comfy.post('/ping', jsonParser, async (request, response) => {361comfy.post('/ping', jsonParser, async (request, response) => {
src/transformers.mjs+0 -6
@@ -31,12 +31,6 @@ const tasks = {
31 configField: 'extras.embeddingModel',31 configField: 'extras.embeddingModel',
32 quantized: true,32 quantized: true,
33 },33 },
34 'text-generation': {
35 defaultModel: 'Cohee/fooocus_expansion-onnx',
36 pipeline: null,
37 configField: 'extras.promptExpansionModel',
38 quantized: false,
39 },
40 'automatic-speech-recognition': {34 'automatic-speech-recognition': {
41 defaultModel: 'Xenova/whisper-small',35 defaultModel: 'Xenova/whisper-small',
42 pipeline: null,36 pipeline: null,