Merge branch 'staging' into persona-lorebook

84d2530c52d6d7ddfa6ddd80f3300111e8f433ee

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

30 files changed, +435 -233Showing whitespace changes
.eslintrc.cjs+1 -0
@@ -54,6 +54,7 @@ module.exports = {
5454 },
5555 // These scripts are loaded in HTML; tell ESLint not to complain about them being undefined
5656 globals: {
57+ globalThis: 'readonly',
5758 ePub: 'readonly',
5859 pdfjsLib: 'readonly',
5960 toastr: 'readonly',
default/config.yaml+2 -0
@@ -1,6 +1,8 @@
11# -- DATA CONFIGURATION --
22# Root directory for user data storage
33dataRoot: ./data
4+# The maximum amount of memory that parsed character cards can use in MB
5+cardsCacheCapacity: 100
46# -- SERVER CONFIGURATION --
57# Listen for incoming connections
68listen: false
package-lock.json+13 -9
@@ -1,12 +1,12 @@
11{
22 "name": "sillytavern",
33 "version": "1.12.89",
44 "lockfileVersion": 3,
55 "requires": true,
66 "packages": {
77 "": {
88 "name": "sillytavern",
99 "version": "1.12.89",
1010 "hasInstallScript": true,
1111 "license": "AGPL-3.0",
1212 "dependencies": {
@@ -3591,9 +3591,9 @@
35913591 "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="
35923592 },
35933593 "node_modules/express": {
35943594 "version": "4.21.12",
35953595 "resolved": "https://registry.npmjs.org/express/-/express-4.21.12.tgz",
35963596 "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+BwuM1IPw8fmQBiQrXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
35973597 "license": "MIT",
35983598 "dependencies": {
35993599 "accepts": "~1.3.8",
@@ -3615,7 +3615,7 @@
36153615 "methods": "~1.1.2",
36163616 "on-finished": "2.4.1",
36173617 "parseurl": "~1.3.3",
36183618 "path-to-regexp": "0.1.1012",
36193619 "proxy-addr": "~2.0.7",
36203620 "qs": "6.13.0",
36213621 "range-parser": "~1.2.1",
@@ -3630,6 +3630,10 @@
36303630 },
36313631 "engines": {
36323632 "node": ">= 0.10.0"
3633+ },
3634+ "funding": {
3635+ "type": "opencollective",
3636+ "url": "https://opencollective.com/express"
36333637 }
36343638 },
36353639 "node_modules/express/node_modules/cookie": {
@@ -5725,9 +5729,9 @@
57255729 }
57265730 },
57275731 "node_modules/path-to-regexp": {
57285732 "version": "0.1.1012",
57295733 "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.1012.tgz",
57305734 "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfaRA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/BSsIwjuh56VU7katFvuM8hULfkwB3FnsTt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/rsVF7PwPKVw1sl5KQS9wbSeOZwt1pmEQ==",
57315735 "license": "MIT"
57325736 },
57335737 "node_modules/peek-readable": {
package.json+1 -1
@@ -84,7 +84,7 @@
8484 "type": "git",
8585 "url": "https://github.com/SillyTavern/SillyTavern.git"
8686 },
8787 "version": "1.12.89",
8888 "scripts": {
8989 "start": "node server.js",
9090 "start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js",
public/global.d.ts+2 -1
@@ -1,4 +1,5 @@
11import libs from './lib';
2+import getContext from './scripts/st-context';
23
34// Global namespace modules
45declare var ai;
@@ -6,7 +7,7 @@ declare var pdfjsLib;
67declare var ePub;
78
89declare var SillyTavern: {
910 getContext(): anytypeof getContext;
1011 llm: any;
1112 libs: typeof libs;
1213};
public/index.html+2 -1
@@ -2987,6 +2987,7 @@
29872987 <optgroup label="Subversions">
29882988 <option value="gemini-exp-1121">Gemini Experimental 2024-11-21</option>
29892989 <option value="gemini-exp-1114">Gemini Experimental 2024-11-14</option>
2990+ <option value="gemini-exp-1206">Gemini Experimental 2024-12-06</option>
29902991 <option value="gemini-1.5-pro-exp-0801">Gemini 1.5 Pro Experiment 2024-08-01</option>
29912992 <option value="gemini-1.5-pro-exp-0827">Gemini 1.5 Pro Experiment 2024-08-27</option>
29922993 <option value="gemini-1.5-pro-latest">Gemini 1.5 Pro [latest]</option>
@@ -4183,7 +4184,7 @@
41834184 </label>
41844185 <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.">
41854186 <input id="show_swipe_num_all_messages" type="checkbox" />
41864187 <small data-i18n="Swipe # for All Messages">Swipe # for All Messages</small><i class="fa-solid fa-mobile-screen-button"></i>
41874188 </label>
41884189 <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">
41894190 <input id="hotswapEnabled" type="checkbox" />
public/jsconfig.json+2 -1
@@ -5,7 +5,8 @@
55 "module": "ESNext",
66 "moduleResolution": "node",
77 "allowUmdGlobalAccess": true,
88 "allowSyntheticDefaultImports": true,
9+ "strictBindCallApply": true
910 },
1011 "exclude": [
1112 "**/node_modules/**",
public/script.js+42 -138
@@ -13,7 +13,7 @@ import {
1313 default as libs,
1414} from './lib.js';
1515
1616import { humanizedDateTime, favsToHotswap, getMessageTimeStamp, dragElement, isMobile, initRossMods, shouldSendOnEnter } from './scripts/RossAscends-mods.js';
1717import { userStatsHandler, statMesProcess, initStats } from './scripts/stats.js';
1818import {
1919 generateKoboldWithStreaming,
@@ -37,8 +37,6 @@ import {
3737 parseTabbyLogprobs,
3838} from './scripts/textgen-settings.js';
3939
40-const { MANCER, TOGETHERAI, OOBA, VLLM, APHRODITE, TABBY, OLLAMA, INFERMATICAI, DREAMGEN, OPENROUTER, FEATHERLESS } = textgen_types;
41-
4240import {
4341 world_info,
4442 getWorldInfoPrompt,
@@ -67,9 +65,7 @@ import {
6765 getGroupChat,
6866 renameGroupMember,
6967 createNewGroupChat,
70- getGroupPastChats,
7168 getGroupAvatar,
72- openGroupChat,
7369 editGroup,
7470 deleteGroupChat,
7571 renameGroupChat,
@@ -98,7 +94,6 @@ import {
9894 resetMovableStyles,
9995 forceCharacterEditorTokenize,
10096 applyPowerUserSettings,
101- switchSwipeNumAllMessages,
10297} from './scripts/power-user.js';
10398
10499import {
@@ -174,8 +169,8 @@ import {
174169} from './scripts/utils.js';
175170import { debounce_timeout } from './scripts/constants.js';
176171
177172import { ModuleWorkerWrapper, doDailyExtensionUpdatesCheck, extension_settings, getContext, initExtensions, loadExtensionSettings, renderExtensionTemplate, renderExtensionTemplateAsync, runGenerationInterceptors, saveMetadataDebounced, writeExtensionField } from './scripts/extensions.js';
178173import { COMMENT_NAME_DEFAULT, executeSlashCommands, executeSlashCommandsOnChatInput, executeSlashCommandsWithOptions, getSlashCommandsHelp, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, processChatSlashCommands, registerSlashCommand, stopScriptExecution } from './scripts/slash-commands.js';
179174import {
180175 tag_map,
181176 tags,
@@ -225,8 +220,8 @@ import {
225220 instruct_presets,
226221 selectContextPreset,
227222} from './scripts/instruct-mode.js';
228223import { initLocales, t, translate } from './scripts/i18n.js';
229224import { getFriendlyTokenizerName, getTokenCount, getTokenCountAsync, getTokenizerModel, initTokenizers, saveTokenCache, TOKENIZER_SUPPORTED_KEY } from './scripts/tokenizers.js';
230225import {
231226 user_avatar,
232227 getUserAvatars,
@@ -242,11 +237,11 @@ import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay
242237import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels, loadTabbyModels } from './scripts/textgen-models.js';
243238import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId, preserveNeutralChat, restoreNeutralChat } from './scripts/chats.js';
244239import { getPresetManager, initPresetManager } from './scripts/preset-manager.js';
245240import { MacrosParser, evaluateMacros, getLastMessageId, initMacros } from './scripts/macros.js';
246241import { currentUser, setUserControls } from './scripts/user.js';
247242import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js';
248243import { renderTemplate, renderTemplateAsync } from './scripts/templates.js';
249244import { initScrapers, ScraperManager } from './scripts/scrapers.js';
250245import { SlashCommandParser } from './scripts/slash-commands/SlashCommandParser.js';
251246import { SlashCommand } from './scripts/slash-commands/SlashCommand.js';
252247import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './scripts/slash-commands/SlashCommandArgument.js';
@@ -268,6 +263,13 @@ import { initServerHistory } from './scripts/server-history.js';
268263import { initSettingsSearch } from './scripts/setting-search.js';
269264import { initBulkEdit } from './scripts/bulk-edit.js';
270265import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js';
266+import { getContext } from './scripts/st-context.js';
267+
268+// API OBJECT FOR EXTERNAL WIRING
269+globalThis.SillyTavern = {
270+ libs,
271+ getContext,
272+};
271273
272274//exporting functions and vars for mods
273275export {
@@ -427,10 +429,6 @@ DOMPurify.addHook('uponSanitizeElement', (node, _, config) => {
427429 }
428430});
429431
430-// API OBJECT FOR EXTERNAL WIRING
431-window['SillyTavern'] = {};
432-window['SillyTavern'].libs = libs;
433-
434432// Event source init
435433export const event_types = {
436434 APP_READY: 'app_ready',
@@ -536,7 +534,7 @@ let displayVersion = 'SillyTavern';
536534
537535let generatedPromptCache = '';
538536let generation_started = new Date();
539537/** @type {import('./scripts/char-data.js').v1CharData[]} */
540538export let characters = [];
541539export let this_chid;
542540let saveCharactersPage = 0;
@@ -812,7 +810,7 @@ export let menu_type = '';
812810export let selected_button = ''; //which button pressed
813811
814812//create pole save
815813export let create_save = {
816814 name: '',
817815 description: '',
818816 creator_notes: '',
@@ -864,7 +862,7 @@ export let amount_gen = 80; //default max length of AI generated responses
864862export let max_context = 2048;
865863
866864var swipes = true;
867865export let extension_prompts = {};
868866
869867export let main_api;// = "kobold";
870868//novel settings
@@ -1175,7 +1173,7 @@ async function getStatusTextgen() {
11751173 return resultCheckStatus();
11761174 }
11771175
11781176 if (textgen_settings.type == textgen_types.OOBA && textgen_settings.bypass_status_check) {
11791177 setOnlineStatus('Status check bypassed');
11801178 return resultCheckStatus();
11811179 }
@@ -1193,34 +1191,34 @@ async function getStatusTextgen() {
11931191
11941192 const data = await response.json();
11951193
11961194 if (textgen_settings.type === textgen_types.MANCER) {
11971195 loadMancerModels(data?.data);
11981196 setOnlineStatus(textgen_settings.mancer_model);
11991197 } else if (textgen_settings.type === textgen_types.TOGETHERAI) {
12001198 loadTogetherAIModels(data?.data);
12011199 setOnlineStatus(textgen_settings.togetherai_model);
12021200 } else if (textgen_settings.type === textgen_types.OLLAMA) {
12031201 loadOllamaModels(data?.data);
12041202 setOnlineStatus(textgen_settings.ollama_model || 'Connected');
12051203 } else if (textgen_settings.type === textgen_types.INFERMATICAI) {
12061204 loadInfermaticAIModels(data?.data);
12071205 setOnlineStatus(textgen_settings.infermaticai_model);
12081206 } else if (textgen_settings.type === textgen_types.DREAMGEN) {
12091207 loadDreamGenModels(data?.data);
12101208 setOnlineStatus(textgen_settings.dreamgen_model);
12111209 } else if (textgen_settings.type === textgen_types.OPENROUTER) {
12121210 loadOpenRouterModels(data?.data);
12131211 setOnlineStatus(textgen_settings.openrouter_model);
12141212 } else if (textgen_settings.type === textgen_types.VLLM) {
12151213 loadVllmModels(data?.data);
12161214 setOnlineStatus(textgen_settings.vllm_model);
12171215 } else if (textgen_settings.type === textgen_types.APHRODITE) {
12181216 loadAphroditeModels(data?.data);
12191217 setOnlineStatus(textgen_settings.aphrodite_model);
12201218 } else if (textgen_settings.type === textgen_types.FEATHERLESS) {
12211219 loadFeatherlessModels(data?.data);
12221220 setOnlineStatus(textgen_settings.featherless_model);
12231221 } else if (textgen_settings.type === textgen_types.TABBY) {
12241222 loadTabbyModels(data?.data);
12251223 setOnlineStatus(textgen_settings.tabby_model || data?.result);
12261224 } else {
@@ -1637,7 +1635,7 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) {
16371635 subEntities = entitiesFilter.applyFilters(subEntities, { clearScoreCache: false, tempOverrides: { [FILTER_TYPES.FOLDER]: FILTER_STATES.UNDEFINED }, clearFuzzySearchCaches: false });
16381636 }
16391637 if (doSort) {
16401638 sortEntitiesList(subEntities, false);
16411639 }
16421640 entity.entities = subEntities;
16431641 entity.hidden = subCount - subEntities.length;
@@ -1665,7 +1663,7 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) {
16651663
16661664 // Sort before returning if requested
16671665 if (doSort) {
16681666 sortEntitiesList(entities, false);
16691667 }
16701668 entitiesFilter.clearFuzzySearchCaches();
16711669 return entities;
@@ -2607,15 +2605,18 @@ export function substituteParams(content, _name1, _name2, _original, _group, _re
26072605 };
26082606 }
26092607
26102608 const getGroupValue = (includeMuted) => {
26112609 if (typeof _group === 'string') {
26122610 return _group;
26132611 }
26142612
26152613 if (selected_group) {
26162614 const members = groups.find(x => x.id === selected_group)?.members;
2615+ /** @type {string[]} */
2616+ const disabledMembers = groups.find(x => x.id === selected_group)?.disabled_members ?? [];
2617+ const isMuted = x => includeMuted ? true : !disabledMembers.includes(x);
26172618 const names = Array.isArray(members)
26182619 ? members.filter(isMuted).map(m => characters.find(c => c.avatar === m)?.name).filter(Boolean).join(', ')
26192620 : '';
26202621 return names;
26212622 } else {
@@ -2639,7 +2640,8 @@ export function substituteParams(content, _name1, _name2, _original, _group, _re
26392640 // Must be substituted last so that they're replaced inside {{description}}
26402641 environment.user = _name1 ?? name1;
26412642 environment.char = _name2 ?? name2;
26422643 environment.group = environment.charIfNotGroup = getGroupValue(true);
2644+ environment.groupNotMuted = getGroupValue(false);
26432645 environment.model = getGeneratingModel();
26442646
26452647 if (additionalMacro && typeof additionalMacro === 'object') {
@@ -5538,7 +5540,7 @@ function extractMultiSwipes(data, type) {
55385540 return swipes;
55395541 }
55405542
55415543 if (main_api === 'openai' || (main_api === 'textgenerationwebui' && [textgen_types.MANCER, textgen_types.VLLM, textgen_types.APHRODITE, textgen_types.TABBY, textgen_types.INFERMATICAI].includes(textgen_settings.type))) {
55425544 if (!Array.isArray(data.choices)) {
55435545 return swipes;
55445546 }
@@ -5701,7 +5703,7 @@ export function cleanUpMessage(getMessage, isImpersonate, isContinue, displayInc
57015703 return getMessage;
57025704}
57035705
57045706export async function saveReply(type, getMessage, fromStreaming, title, swipes) {
57055707 if (type != 'append' && type != 'continue' && type != 'appendFinal' && chat.length && (chat[chat.length - 1]['swipe_id'] === undefined ||
57065708 chat[chat.length - 1]['is_user'])) {
57075709 type = 'normal';
@@ -7660,14 +7662,12 @@ export function showSwipeButtons() {
76607662 //allows for writing individual swipe counters for past messages
76617663 const lastSwipeCounter = $('.last_mes .swipes-counter');
76627664 lastSwipeCounter.text(swipeCounterText).show();
7663-
7664- switchSwipeNumAllMessages();
76657665}
76667666
76677667export function hideSwipeButtons() {
76687668 $('#chat')chatElement.find('.swipe_right').hide();
76697669 $('#chat')chatElement.find('.last_mes .swipes-counter').hide();
76707670 $('#chat')chatElement.find('.swipe_left').hide();
76717671}
76727672
76737673/**
@@ -8233,102 +8233,6 @@ async function createOrEditCharacter(e) {
82338233 }
82348234}
82358235
8236-window['SillyTavern'].getContext = function () {
8237- return {
8238- chat: chat,
8239- characters: characters,
8240- groups: groups,
8241- name1: name1,
8242- name2: name2,
8243- characterId: this_chid,
8244- groupId: selected_group,
8245- chatId: selected_group
8246- ? groups.find(x => x.id == selected_group)?.chat_id
8247- : (this_chid && characters[this_chid] && characters[this_chid].chat),
8248- getCurrentChatId: getCurrentChatId,
8249- getRequestHeaders: getRequestHeaders,
8250- reloadCurrentChat: reloadCurrentChat,
8251- renameChat: renameChat,
8252- saveSettingsDebounced: saveSettingsDebounced,
8253- onlineStatus: online_status,
8254- maxContext: Number(max_context),
8255- chatMetadata: chat_metadata,
8256- streamingProcessor,
8257- eventSource: eventSource,
8258- eventTypes: event_types,
8259- addOneMessage: addOneMessage,
8260- generate: Generate,
8261- sendStreamingRequest: sendStreamingRequest,
8262- sendGenerationRequest: sendGenerationRequest,
8263- stopGeneration: stopGeneration,
8264- getTokenCount: getTokenCount,
8265- extensionPrompts: extension_prompts,
8266- setExtensionPrompt: setExtensionPrompt,
8267- updateChatMetadata: updateChatMetadata,
8268- saveChat: saveChatConditional,
8269- openCharacterChat: openCharacterChat,
8270- openGroupChat: openGroupChat,
8271- saveMetadata: saveMetadata,
8272- sendSystemMessage: sendSystemMessage,
8273- activateSendButtons,
8274- deactivateSendButtons,
8275- saveReply,
8276- substituteParams,
8277- substituteParamsExtended,
8278- SlashCommandParser,
8279- SlashCommand,
8280- SlashCommandArgument,
8281- SlashCommandNamedArgument,
8282- ARGUMENT_TYPE,
8283- executeSlashCommandsWithOptions,
8284- /** @deprecated Use SlashCommandParser.addCommandObject() instead */
8285- registerSlashCommand: registerSlashCommand,
8286- /** @deprecated Use executeSlashCommandWithOptions instead */
8287- executeSlashCommands: executeSlashCommands,
8288- timestampToMoment: timestampToMoment,
8289- /** @deprecated Handlebars for extensions are no longer supported. */
8290- registerHelper: () => { },
8291- registerMacro: MacrosParser.registerMacro.bind(MacrosParser),
8292- unregisterMacro: MacrosParser.unregisterMacro.bind(MacrosParser),
8293- registerFunctionTool: ToolManager.registerFunctionTool.bind(ToolManager),
8294- unregisterFunctionTool: ToolManager.unregisterFunctionTool.bind(ToolManager),
8295- isToolCallingSupported: ToolManager.isToolCallingSupported.bind(ToolManager),
8296- canPerformToolCalls: ToolManager.canPerformToolCalls.bind(ToolManager),
8297- registerDebugFunction: registerDebugFunction,
8298- /** @deprecated Use renderExtensionTemplateAsync instead. */
8299- renderExtensionTemplate: renderExtensionTemplate,
8300- renderExtensionTemplateAsync: renderExtensionTemplateAsync,
8301- registerDataBankScraper: ScraperManager.registerDataBankScraper,
8302- /** @deprecated Use callGenericPopup or Popup instead. */
8303- callPopup: callPopup,
8304- callGenericPopup: callGenericPopup,
8305- showLoader: showLoader,
8306- hideLoader: hideLoader,
8307- mainApi: main_api,
8308- extensionSettings: extension_settings,
8309- ModuleWorkerWrapper: ModuleWorkerWrapper,
8310- getTokenizerModel: getTokenizerModel,
8311- generateQuietPrompt: generateQuietPrompt,
8312- writeExtensionField: writeExtensionField,
8313- getThumbnailUrl: getThumbnailUrl,
8314- selectCharacterById: selectCharacterById,
8315- messageFormatting: messageFormatting,
8316- shouldSendOnEnter: shouldSendOnEnter,
8317- isMobile: isMobile,
8318- t: t,
8319- translate: translate,
8320- tags: tags,
8321- tagMap: tag_map,
8322- menuType: menu_type,
8323- createCharacterData: create_save,
8324- /** @deprecated Legacy snake-case naming, compatibility with old extensions */
8325- event_types: event_types,
8326- Popup: Popup,
8327- POPUP_TYPE: POPUP_TYPE,
8328- POPUP_RESULT: POPUP_RESULT,
8329- };
8330-};
8331-
83328236/**
83338237 * Formats a counter for a swipe view.
83348238 * @param {number} current The current number of items.
public/scripts/char-data.js+1 -0
@@ -75,6 +75,7 @@
7575 * @property {string} [source_url] - The source URL associated with the character.
7676 * @property {{full_path: string}} [chub] - The Chub-specific data associated with the character.
7777 * @property {{source: string[]}} [risuai] - The RisuAI-specific data associated with the character.
78+ * @property {{positive: string, negative: string}} [sd_character_prompt] - SD-specific data associated with the character.
7879 */
7980
8081/**
public/scripts/extensions.js+1 -1
@@ -5,6 +5,7 @@ import { showLoader } from './loader.js';
55import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
66import { renderTemplate, renderTemplateAsync } from './templates.js';
77import { isSubsetOf, setValueByPath } from './utils.js';
8+import { getContext } from './st-context.js';
89export {
910 getContext,
1011 getApiUrl,
@@ -174,7 +175,6 @@ const extension_settings = {
174175let modules = [];
175176let activeExtensions = new Set();
176177
177-const getContext = () => window['SillyTavern'].getContext();
178178const getApiUrl = () => extension_settings.apiUrl;
179179let connectedToApi = false;
180180
public/scripts/extensions/caption/settings.html+1 -0
@@ -62,6 +62,7 @@
6262 <option data-type="google" value="gemini-1.5-flash-8b-exp-0924">gemini-1.5-flash-8b-exp-0924</option>
6363 <option data-type="google" value="gemini-exp-1114">gemini-exp-1114</option>
6464 <option data-type="google" value="gemini-exp-1121">gemini-exp-1121</option>
65+ <option data-type="google" value="gemini-exp-1206">gemini-exp-1206</option>
6566 <option data-type="google" value="gemini-1.5-pro">gemini-1.5-pro</option>
6667 <option data-type="google" value="gemini-1.5-pro-latest">gemini-1.5-pro-latest</option>
6768 <option data-type="google" value="gemini-1.5-pro-001">gemini-1.5-pro-001</option>
public/scripts/extensions/stable-diffusion/index.js+0 -15
@@ -60,7 +60,6 @@ import { ToolManager } from '../../tool-calling.js';
6060export { MODULE_NAME };
6161
6262const MODULE_NAME = 'sd';
63-const UPDATE_INTERVAL = 1000;
6463// This is a 1x1 transparent PNG
6564const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
6665const CUSTOM_STOP_EVENT = 'sd_stop_generation';
@@ -3687,8 +3686,6 @@ async function addSDGenButtons() {
36873686 const button = $('#sd_gen');
36883687 const dropdown = $('#sd_dropdown');
36893688 dropdown.hide();
3690- button.hide();
3691- messageButton.hide();
36923689
36933690 let popper = Popper.createPopper(button.get(0), dropdown.get(0), {
36943691 placement: 'top',
@@ -3770,18 +3767,6 @@ function isValidState() {
37703767 }
37713768}
37723769
3773-async function moduleWorker() {
3774- if (isValidState()) {
3775- $('#sd_gen').show();
3776- $('.sd_message_gen').show();
3777- }
3778- else {
3779- $('#sd_gen').hide();
3780- $('.sd_message_gen').hide();
3781- }
3782-}
3783-
3784-setInterval(moduleWorker, UPDATE_INTERVAL);
37853770let buttonAbortController = null;
37863771
37873772async function sdMessageButton(e) {
public/scripts/extensions/stable-diffusion/style.css+1 -1
@@ -4,7 +4,7 @@
44
55#sd_dropdown {
66 z-index: 30000;
77 backdrop-filter: blur(var(--SmartThemeBlurStrength));
88}
99
1010#sd_comfy_open_workflow_editor {
public/scripts/extensions/tts/style.css+1 -1
@@ -93,7 +93,7 @@
9393.at-settings-separator {
9494 margin-top: 10px;
9595 margin-bottom: 10px;
9696 padding: 18x18px;
9797 font-weight: bold;
9898 border-top: 1px solid #e1e1e1; /* Grey line */
9999 border-bottom: 1px solid #e1e1e1; /* Grey line */
public/scripts/group-chats.js+8 -6
@@ -1265,18 +1265,20 @@ function getGroupCharacters({ doFilter, onlyMembers } = {}) {
12651265 const thisGroup = openGroupId && groups.find((x) => x.id == openGroupId);
12661266 let candidates = characters
12671267 .filter((x) => isGroupMember(thisGroup, x.avatar) == onlyMembers)
12681268 .map((x, index) => ({ item: x, id: indexcharacters.indexOf(x), type: 'character' }));
1269+
1270+ if (doFilter) {
1271+ candidates = groupCandidatesFilter.applyFilters(candidates);
1272+ }
12691273
12701274 if (onlyMembers) {
12711275 candidates.sort(sortMembersFn);
12721276 } else {
1273- sortEntitiesList(candidates);
1277+ const useFilterOrder = doFilter && !!$('#rm_group_filter').val();
1274- }
1278+ sortEntitiesList(candidates, useFilterOrder, groupCandidatesFilter);
1275-
1276- if (doFilter) {
1277- candidates = groupCandidatesFilter.applyFilters(candidates);
12781279 }
12791280
1281+ groupCandidatesFilter.clearFuzzySearchCaches();
12801282 return candidates;
12811283}
12821284
public/scripts/openai.js+4 -3
@@ -379,8 +379,8 @@ export let selected_proxy = proxies[0];
379379let openai_setting_names;
380380let openai_settings;
381381
382-
382+/** @type {import('./PromptManager.js').PromptManager} */
383383export let promptManager = null;
384384
385385async function validateReverseProxy() {
386386 if (!oai_settings.reverse_proxy) {
@@ -4077,7 +4077,7 @@ async function onModelChange() {
40774077 $('#openai_max_context').attr('max', max_2mil);
40784078 } else if (value.includes('gemini-exp-1114') || value.includes('gemini-exp-1121')) {
40794079 $('#openai_max_context').attr('max', max_32k);
40804080 } else if (value.includes('gemini-1.5-pro') || value.includes('gemini-exp-1206')) {
40814081 $('#openai_max_context').attr('max', max_2mil);
40824082 } else if (value.includes('gemini-1.5-flash')) {
40834083 $('#openai_max_context').attr('max', max_1mil);
@@ -4761,6 +4761,7 @@ export function isImageInliningSupported() {
47614761 'gemini-1.5-flash-8b-exp-0924',
47624762 'gemini-exp-1114',
47634763 'gemini-exp-1121',
4764+ 'gemini-exp-1206',
47644765 'gemini-1.0-pro-vision-latest',
47654766 'gemini-1.5-pro',
47664767 'gemini-1.5-pro-latest',
public/scripts/power-user.js+12 -11
@@ -60,7 +60,6 @@ export {
6060 loadMovingUIState,
6161 collapseNewlines,
6262 playMessageSound,
63- sortEntitiesList,
6463 fixMarkdown,
6564 power_user,
6665 send_on_enter_options,
@@ -474,9 +473,9 @@ function switchCompactInputArea() {
474473 $('#compact_input_area').prop('checked', power_user.compact_input_area);
475474}
476475
477476export function switchSwipeNumAllMessages() {
478477 $('#show_swipe_num_all_messages').prop('checked', power_user.show_swipe_num_all_messages);
479478 $('.mes:not(.last_mes) .swipes-counterbody').csstoggleClass('opacityswipeAllMessages', '').toggle(!!power_user.show_swipe_num_all_messages);
480479}
481480
482481var originalSliderValues = [];
@@ -1838,7 +1837,7 @@ async function loadContextSettings() {
18381837 * Common function to perform fuzzy search with optional caching
18391838 * @param {string} type - Type of search from fuzzySearchCategories
18401839 * @param {any[]} data - Data array to search in
18411840 * @param {Array<{name: string, weight: number, getFn?: Function(obj: any) => string}>} keys - Fuse.js keys configuration
18421841 * @param {string} searchValue - The search term
18431842 * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
18441843 * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score
@@ -1852,7 +1851,6 @@ function performFuzzySearch(type, data, keys, searchValue, fuzzySearchCaches = n
18521851 }
18531852 }
18541853
1855- // @ts-ignore
18561854 const fuse = new Fuse(data, {
18571855 keys: keys,
18581856 includeScore: true,
@@ -2081,19 +2079,22 @@ const compareFunc = (first, second) => {
20812079/**
20822080 * Sorts an array of entities based on the current sort settings
20832081 * @param {any[]} entities An array of objects with an `item` property
2082+ * @param {boolean} forceSearch Whether to force search sorting
2083+ * @param {import('./filters.js').FilterHelper} [filterHelper=null] Filter helper to use
20842084 */
20852085export function sortEntitiesList(entities, forceSearch, filterHelper = null) {
2086+ filterHelper = filterHelper ?? entitiesFilter;
20862087 if (power_user.sort_field == undefined || entities.length === 0) {
20872088 return;
20882089 }
20892090
2090- if (power_user.sort_order === 'random') {
2091+ const isSearch = forceSearch || $('#character_sort_order option[data-field="search"]').is(':selected');
2092+
2093+ if (!isSearch && power_user.sort_order === 'random') {
20912094 shuffle(entities);
20922095 return;
20932096 }
20942097
2095- const isSearch = $('#character_sort_order option[data-field="search"]').is(':selected');
2096-
20972098 entities.sort((a, b) => {
20982099 // Sort tags/folders will always be at the top
20992100 if (a.type === 'tag' && b.type !== 'tag') {
@@ -2105,8 +2106,8 @@ function sortEntitiesList(entities) {
21052106
21062107 // If we have search sorting, we take scores and use those
21072108 if (isSearch) {
21082109 const aScore = entitiesFilterfilterHelper.getScore(FILTER_TYPES.SEARCH, `${a.type}.${a.id}`);
21092110 const bScore = entitiesFilterfilterHelper.getScore(FILTER_TYPES.SEARCH, `${b.type}.${b.id}`);
21102111 return (aScore - bScore);
21112112 }
21122113
public/scripts/slash-commands.js+113 -4
@@ -52,7 +52,7 @@ import { hideChatMessageRange } from './chats.js';
5252import { getContext, saveMetadataDebounced } from './extensions.js';
5353import { getRegexedString, regex_placement } from './extensions/regex/engine.js';
5454import { findGroupMemberId, groups, is_group_generating, openGroupById, resetSelectedGroup, saveGroupChat, selected_group } from './group-chats.js';
5555import { chat_completion_sources, oai_settings, setupChatCompletionPromptManagerpromptManager } from './openai.js';
5656import { autoSelectPersona, retriggerFirstMessageOnEmptyChat, setPersonaLockState, togglePersonaLock, user_avatar } from './personas.js';
5757import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js';
5858import { SERVER_INPUTS, textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
@@ -1699,6 +1699,49 @@ export function initDefaultSlashCommands() {
16991699 helpString: 'Sets the model for the current API. Gets the current model name if no argument is provided.',
17001700 }));
17011701 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1702+ name: 'getpromptentry',
1703+ aliases: ['getpromptentries'],
1704+ callback: getPromptEntryCallback,
1705+ returns: 'true/false state of prompt(s)',
1706+ namedArgumentList: [
1707+ SlashCommandNamedArgument.fromProps({
1708+ name: 'identifier',
1709+ description: 'Prompt entry identifier(s) to retrieve',
1710+ typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST],
1711+ acceptsMultiple: true,
1712+ enumProvider: () =>
1713+ promptManager.serviceSettings.prompts
1714+ .map(prompt => prompt.identifier)
1715+ .map(identifier => new SlashCommandEnumValue(identifier)),
1716+ }),
1717+ SlashCommandNamedArgument.fromProps({
1718+ name: 'name',
1719+ description: 'Prompt entry name(s) to retrieve',
1720+ typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST],
1721+ acceptsMultiple: true,
1722+ enumProvider: () =>
1723+ promptManager.serviceSettings.prompts
1724+ .map(prompt => prompt.name)
1725+ .map(name => new SlashCommandEnumValue(name)),
1726+ }),
1727+ SlashCommandNamedArgument.fromProps({
1728+ name: 'return',
1729+ description: 'Whether the return will be simple, a list, or a dict.',
1730+ typeList: [ARGUMENT_TYPE.STRING],
1731+ defaultValue: 'simple',
1732+ enumList: ['simple', 'list', 'dict'],
1733+ }),
1734+ ],
1735+ helpString: `
1736+ <div>
1737+ Gets the state of the specified prompt entries.
1738+ </div>
1739+ <div>
1740+ If <code>return</code> is <code>simple</code> (default) then the return will be a single value if only one value was retrieved; otherwise uses a dict (if the identifier parameter was used) or a list.
1741+ </div>
1742+ `,
1743+ }));
1744+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
17021745 name: 'setpromptentry',
17031746 aliases: ['setpromptentries'],
17041747 callback: setPromptEntryCallback,
@@ -1709,7 +1752,6 @@ export function initDefaultSlashCommands() {
17091752 typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST],
17101753 acceptsMultiple: true,
17111754 enumProvider: () => {
1712- const promptManager = setupChatCompletionPromptManager(oai_settings);
17131755 const prompts = promptManager.serviceSettings.prompts;
17141756 return prompts.map(prompt => new SlashCommandEnumValue(prompt.identifier, prompt.name, enumTypes.enum));
17151757 },
@@ -1720,7 +1762,6 @@ export function initDefaultSlashCommands() {
17201762 typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST],
17211763 acceptsMultiple: true,
17221764 enumProvider: () => {
1723- const promptManager = setupChatCompletionPromptManager(oai_settings);
17241765 const prompts = promptManager.serviceSettings.prompts;
17251766 return prompts.map(prompt => new SlashCommandEnumValue(prompt.name, prompt.identifier, enumTypes.enum));
17261767 },
@@ -3787,6 +3828,75 @@ function modelCallback(args, model) {
37873828}
37883829
37893830/**
3831+ * Gets the state of prompt entries (toggles) either via identifier/uuid or name.
3832+ * @param {object} args Object containing arguments
3833+ * @param {string} args.identifier Select prompt entry using an identifier (uuid)
3834+ * @param {string} args.name Select prompt entry using name
3835+ * @param {string} args.return The type of return value to use (simple, list, dict)
3836+ * @returns {Object} An object containing the states of the requested prompt entries
3837+ */
3838+function getPromptEntryCallback(args) {
3839+ const prompts = promptManager.serviceSettings.prompts;
3840+ let returnType = args.return ?? 'simple';
3841+
3842+ function parseArgs(arg) {
3843+ // Arg is already an array
3844+ if (Array.isArray(arg)) {
3845+ return arg;
3846+ }
3847+ const list = [];
3848+ try {
3849+ // Arg is a JSON-stringified array
3850+ const parsedArg = JSON.parse(arg);
3851+ list.push(...Array.isArray(parsedArg) ? parsedArg : [arg]);
3852+ } catch {
3853+ // Arg is a string
3854+ list.push(arg);
3855+ }
3856+ return list;
3857+ }
3858+
3859+ let identifiersList = parseArgs(args.identifier);
3860+ let nameList = parseArgs(args.name);
3861+
3862+ // Check if identifiers exists in prompt, else remove from list
3863+ if (identifiersList.length !== 0) {
3864+ identifiersList = identifiersList.filter(identifier => prompts.some(prompt => prompt.identifier === identifier));
3865+ }
3866+
3867+ if (nameList.length !== 0) {
3868+ nameList.forEach(name => {
3869+ let identifiers = prompts
3870+ .filter(entry => entry.name === name)
3871+ .map(entry => entry.identifier);
3872+ identifiersList = identifiersList.concat(identifiers);
3873+ });
3874+ }
3875+
3876+ // Get the state for each prompt entry
3877+ let promptStates = new Map();
3878+ identifiersList.forEach(identifier => {
3879+ const promptOrderEntry = promptManager.getPromptOrderEntry(promptManager.activeCharacter, identifier);
3880+ if (promptOrderEntry) {
3881+ promptStates.set(identifier, promptOrderEntry.enabled);
3882+ }
3883+ });
3884+
3885+ // If return is simple (default) but more than one prompt state was retrieved, then change return type
3886+ if (returnType === 'simple' && promptStates.size > 1) {
3887+ returnType = args.identifier ? 'dict' : 'list';
3888+ }
3889+
3890+ const result = (() => {
3891+ if (returnType === 'list') return [...promptStates.values()];
3892+ if (returnType === 'dict') return Object.fromEntries(promptStates);
3893+ return [...promptStates.values()][0];
3894+ })();
3895+
3896+ return result;
3897+}
3898+
3899+/**
37903900 * Sets state of prompt entries (toggles) either via identifier/uuid or name.
37913901 * @param {object} args Object containing arguments
37923902 * @param {string} args.identifier Select prompt entry using an identifier (uuid)
@@ -3796,7 +3906,6 @@ function modelCallback(args, model) {
37963906 */
37973907function setPromptEntryCallback(args, targetState) {
37983908 // needs promptManager to manipulate prompt entries
3799- const promptManager = setupChatCompletionPromptManager(oai_settings);
38003909 const prompts = promptManager.serviceSettings.prompts;
38013910
38023911 function parseArgs(arg) {
public/scripts/st-context.js+171 -0
@@ -0,0 +1,171 @@
1+import {
2+ activateSendButtons,
3+ addOneMessage,
4+ callPopup,
5+ characters,
6+ chat,
7+ chat_metadata,
8+ create_save,
9+ deactivateSendButtons,
10+ event_types,
11+ eventSource,
12+ extension_prompts,
13+ Generate,
14+ generateQuietPrompt,
15+ getCurrentChatId,
16+ getRequestHeaders,
17+ getThumbnailUrl,
18+ main_api,
19+ max_context,
20+ menu_type,
21+ messageFormatting,
22+ name1,
23+ name2,
24+ online_status,
25+ openCharacterChat,
26+ reloadCurrentChat,
27+ renameChat,
28+ saveChatConditional,
29+ saveMetadata,
30+ saveReply,
31+ saveSettingsDebounced,
32+ selectCharacterById,
33+ sendGenerationRequest,
34+ sendStreamingRequest,
35+ sendSystemMessage,
36+ setExtensionPrompt,
37+ stopGeneration,
38+ streamingProcessor,
39+ substituteParams,
40+ substituteParamsExtended,
41+ this_chid,
42+ updateChatMetadata,
43+} from '../script.js';
44+import {
45+ extension_settings,
46+ ModuleWorkerWrapper,
47+ renderExtensionTemplate,
48+ renderExtensionTemplateAsync,
49+ writeExtensionField,
50+} from './extensions.js';
51+import { groups, openGroupChat, selected_group } from './group-chats.js';
52+import { t, translate } from './i18n.js';
53+import { hideLoader, showLoader } from './loader.js';
54+import { MacrosParser } from './macros.js';
55+import { oai_settings } from './openai.js';
56+import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
57+import { power_user, registerDebugFunction } from './power-user.js';
58+import { isMobile, shouldSendOnEnter } from './RossAscends-mods.js';
59+import { ScraperManager } from './scrapers.js';
60+import { executeSlashCommands, executeSlashCommandsWithOptions, registerSlashCommand } from './slash-commands.js';
61+import { SlashCommand } from './slash-commands/SlashCommand.js';
62+import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
63+import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
64+import { tag_map, tags } from './tags.js';
65+import { textgenerationwebui_settings } from './textgen-settings.js';
66+import { getTokenCount, getTokenCountAsync, getTokenizerModel } from './tokenizers.js';
67+import { ToolManager } from './tool-calling.js';
68+import { timestampToMoment } from './utils.js';
69+
70+export function getContext() {
71+ return {
72+ chat,
73+ characters,
74+ groups,
75+ name1,
76+ name2,
77+ characterId: this_chid,
78+ groupId: selected_group,
79+ chatId: selected_group
80+ ? groups.find(x => x.id == selected_group)?.chat_id
81+ : (characters[this_chid]?.chat),
82+ getCurrentChatId,
83+ getRequestHeaders,
84+ reloadCurrentChat,
85+ renameChat,
86+ saveSettingsDebounced,
87+ onlineStatus: online_status,
88+ maxContext: Number(max_context),
89+ chatMetadata: chat_metadata,
90+ streamingProcessor,
91+ eventSource,
92+ eventTypes: event_types,
93+ addOneMessage,
94+ generate: Generate,
95+ sendStreamingRequest,
96+ sendGenerationRequest,
97+ stopGeneration,
98+ /** @deprecated Use getTokenCountAsync instead */
99+ getTokenCount,
100+ getTokenCountAsync,
101+ extensionPrompts: extension_prompts,
102+ setExtensionPrompt,
103+ updateChatMetadata,
104+ saveChat: saveChatConditional,
105+ openCharacterChat,
106+ openGroupChat,
107+ saveMetadata,
108+ sendSystemMessage,
109+ activateSendButtons,
110+ deactivateSendButtons,
111+ saveReply,
112+ substituteParams,
113+ substituteParamsExtended,
114+ SlashCommandParser,
115+ SlashCommand,
116+ SlashCommandArgument,
117+ SlashCommandNamedArgument,
118+ ARGUMENT_TYPE,
119+ executeSlashCommandsWithOptions,
120+ /** @deprecated Use SlashCommandParser.addCommandObject() instead */
121+ registerSlashCommand,
122+ /** @deprecated Use executeSlashCommandWithOptions instead */
123+ executeSlashCommands,
124+ timestampToMoment,
125+ /** @deprecated Handlebars for extensions are no longer supported. */
126+ registerHelper: () => { },
127+ registerMacro: MacrosParser.registerMacro.bind(MacrosParser),
128+ unregisterMacro: MacrosParser.unregisterMacro.bind(MacrosParser),
129+ registerFunctionTool: ToolManager.registerFunctionTool.bind(ToolManager),
130+ unregisterFunctionTool: ToolManager.unregisterFunctionTool.bind(ToolManager),
131+ isToolCallingSupported: ToolManager.isToolCallingSupported.bind(ToolManager),
132+ canPerformToolCalls: ToolManager.canPerformToolCalls.bind(ToolManager),
133+ registerDebugFunction,
134+ /** @deprecated Use renderExtensionTemplateAsync instead. */
135+ renderExtensionTemplate,
136+ renderExtensionTemplateAsync,
137+ registerDataBankScraper: ScraperManager.registerDataBankScraper.bind(ScraperManager),
138+ /** @deprecated Use callGenericPopup or Popup instead. */
139+ callPopup,
140+ callGenericPopup,
141+ showLoader,
142+ hideLoader,
143+ mainApi: main_api,
144+ extensionSettings: extension_settings,
145+ ModuleWorkerWrapper,
146+ getTokenizerModel,
147+ generateQuietPrompt,
148+ writeExtensionField,
149+ getThumbnailUrl,
150+ selectCharacterById,
151+ messageFormatting,
152+ shouldSendOnEnter,
153+ isMobile,
154+ t,
155+ translate,
156+ tags,
157+ tagMap: tag_map,
158+ menuType: menu_type,
159+ createCharacterData: create_save,
160+ /** @deprecated Legacy snake-case naming, compatibility with old extensions */
161+ event_types: event_types,
162+ Popup,
163+ POPUP_TYPE,
164+ POPUP_RESULT,
165+ chatCompletionSettings: oai_settings,
166+ textCompletionSettings: textgenerationwebui_settings,
167+ powerUserSettings: power_user,
168+ };
169+}
170+
171+export default getContext;
public/scripts/templates/macros.html+2 -1
@@ -21,7 +21,8 @@
2121 <li><tt>&lcub;&lcub;user&rcub;&rcub;</tt> – <span data-i18n="help_macros_15">your current Persona username</span></li>
2222 <li><tt>&lcub;&lcub;char&rcub;&rcub;</tt> – <span data-i18n="help_macros_16">the Character's name</span></li>
2323 <li><tt>&lcub;&lcub;char_version&rcub;&rcub;</tt> – <span data-i18n="help_macros_17">the Character's version number</span></li>
2424 <li><tt>&lcub;&lcub;group&rcub;&rcub;</tt> – <span data-i18n="help_macros_18">a comma-separated list of group member names (including muted) or the character name in solo chats. Alias: &lcub;&lcub;charIfNotGroup&rcub;&rcub;</span></li>
25+ <li><tt>&lcub;&lcub;groupNotMuted&rcub;&rcub;</tt> – <span data-i18n="help_groupNotMuted">the same as &lcub;&lcub;group&rcub;&rcub;, but excludes muted members</span></li>
2526 <li><tt>&lcub;&lcub;model&rcub;&rcub;</tt> – <span data-i18n="help_macros_19">a text generation model name for the currently selected API. </span><b data-i18n="Can be inaccurate!">Can be inaccurate!</b></li>
2627 <li><tt>&lcub;&lcub;lastMessage&rcub;&rcub;</tt> – <span data-i18n="help_macros_20">the text of the latest chat message.</span></li>
2728 <li><tt>&lcub;&lcub;lastUserMessage&rcub;&rcub;</tt> – <span data-i18n="help_macros_lastUser">the text of the latest user chat message.</span></li>
public/scripts/textgen-settings.js+7 -10
@@ -18,13 +18,6 @@ import { getCurrentDreamGenModelTokenizer, getCurrentOpenRouterModelTokenizer }
1818import { ENCODE_TOKENIZERS, TEXTGEN_TOKENIZERS, getTextTokens, tokenizers } from './tokenizers.js';
1919import { getSortableDelay, onlyUnique } from './utils.js';
2020
21-export {
22- settings as textgenerationwebui_settings,
23- loadTextGenSettings,
24- generateTextGenWithStreaming,
25- formatTextGenURL,
26-};
27-
2821export const textgen_types = {
2922 OOBA: 'ooba',
3023 MANCER: 'mancer',
@@ -197,6 +190,10 @@ const settings = {
197190 featherless_model: '',
198191};
199192
193+export {
194+ settings as textgenerationwebui_settings,
195+};
196+
200197export let textgenerationwebui_banned_in_macros = [];
201198
202199export let textgenerationwebui_presets = [];
@@ -327,7 +324,7 @@ async function selectPreset(name) {
327324 saveSettingsDebounced();
328325}
329326
330327export function formatTextGenURL(value) {
331328 try {
332329 const noFormatTypes = [MANCER, TOGETHERAI, INFERMATICAI, DREAMGEN, OPENROUTER];
333330 if (noFormatTypes.includes(settings.type)) {
@@ -465,7 +462,7 @@ function calculateLogitBias() {
465462 return result;
466463}
467464
468465export function loadTextGenSettings(data, loadedSettings) {
469466 textgenerationwebui_presets = convertPresets(data.textgenerationwebui_presets);
470467 textgenerationwebui_preset_names = data.textgenerationwebui_preset_names ?? [];
471468 Object.assign(settings, loadedSettings.textgenerationwebui_settings ?? {});
@@ -889,7 +886,7 @@ function setSettingByName(setting, value, trigger) {
889886 * @returns {Promise<(function(): AsyncGenerator<{swipes: [], text: string, toolCalls: [], logprobs: {token: string, topLogprobs: Candidate[]}|null}, void, *>)|*>}
890887 * @throws {Error} - If the response status is not OK, or from within the generator
891888 */
892889export async function generateTextGenWithStreaming(generate_data, signal) {
893890 generate_data.stream = true;
894891
895892 const response = await fetch('/api/backends/text-completions/generate', {
public/scripts/tokenizers.js+18 -8
@@ -8,8 +8,6 @@ import { kai_flags } from './kai-settings.js';
88import { textgen_types, textgenerationwebui_settings as textgen_settings, getTextGenServer, getTextGenModel } from './textgen-settings.js';
99import { getCurrentDreamGenModelTokenizer, getCurrentOpenRouterModelTokenizer, openRouterModels } from './textgen-models.js';
1010
11-const { OOBA, TABBY, KOBOLDCPP, VLLM, APHRODITE, LLAMACPP, OPENROUTER, DREAMGEN } = textgen_types;
12-
1311export const CHARACTERS_PER_TOKEN_RATIO = 3.35;
1412export const TOKENIZER_WARNING_KEY = 'tokenizationWarningShown';
1513export const TOKENIZER_SUPPORTED_KEY = 'tokenizationSupported';
@@ -52,8 +50,12 @@ export const ENCODE_TOKENIZERS = [
5250 //tokenizers.NERD2,
5351];
5452
55-// A list of Text Completion sources that support remote tokenization.
53+/**
56-export const TEXTGEN_TOKENIZERS = [OOBA, TABBY, KOBOLDCPP, LLAMACPP, VLLM, APHRODITE];
54+ * A list of Text Completion sources that support remote tokenization.
55+ * Populated in initTokenziers due to circular dependencies.
56+ * @type {string[]}
57+ */
58+export const TEXTGEN_TOKENIZERS = [];
5759
5860const TOKENIZER_URLS = {
5961 [tokenizers.GPT2]: {
@@ -287,7 +289,7 @@ export function getTokenizerBestMatch(forApi) {
287289 const hasTokenizerError = sessionStorage.getItem(TOKENIZER_WARNING_KEY);
288290 const hasValidEndpoint = sessionStorage.getItem(TOKENIZER_SUPPORTED_KEY);
289291 const isConnected = online_status !== 'no_connection';
290292 const isTokenizerSupported = TEXTGEN_TOKENIZERS.includes(textgen_settings.type) && (textgen_settings.type !== textgen_types.OOBA || hasValidEndpoint);
291293
292294 if (!hasTokenizerError && isConnected) {
293295 if (forApi === 'kobold' && kai_flags.can_use_tokenization) {
@@ -297,10 +299,10 @@ export function getTokenizerBestMatch(forApi) {
297299 if (forApi === 'textgenerationwebui' && isTokenizerSupported) {
298300 return tokenizers.API_TEXTGENERATIONWEBUI;
299301 }
300302 if (forApi === 'textgenerationwebui' && textgen_settings.type === textgen_types.OPENROUTER) {
301303 return getCurrentOpenRouterModelTokenizer();
302304 }
303305 if (forApi === 'textgenerationwebui' && textgen_settings.type === textgen_types.DREAMGEN) {
304306 return getCurrentDreamGenModelTokenizer();
305307 }
306308 }
@@ -576,7 +578,7 @@ export function getTokenizerModel() {
576578
577579 // And for OpenRouter (if not a site model, then it's impossible to determine the tokenizer)
578580 if (main_api == 'openai' && oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER && oai_settings.openrouter_model ||
579581 main_api == 'textgenerationwebui' && textgen_settings.type === textgen_types.OPENROUTER && textgen_settings.openrouter_model) {
580582 const model = main_api == 'openai'
581583 ? model_list.find(x => x.id === oai_settings.openrouter_model)
582584 : openRouterModels.find(x => x.id === textgen_settings.openrouter_model);
@@ -1121,6 +1123,14 @@ export function decodeTextTokens(tokenizerType, ids) {
11211123}
11221124
11231125export async function initTokenizers() {
1126+ TEXTGEN_TOKENIZERS.push(
1127+ textgen_types.OOBA,
1128+ textgen_types.TABBY,
1129+ textgen_types.KOBOLDCPP,
1130+ textgen_types.LLAMACPP,
1131+ textgen_types.VLLM,
1132+ textgen_types.APHRODITE,
1133+ );
11241134 await loadTokenCache();
11251135 registerDebugFunction('resetTokenCache', 'Reset token cache', 'Purges the calculated token counts. Use this if you want to force a full re-tokenization of all chats or suspect the token counts are wrong.', resetTokenCache);
11261136}
public/scripts/tool-calling.js+1 -1
@@ -947,7 +947,7 @@ export class ToolManager {
947947 enumProvider: toolsEnumProvider,
948948 }),
949949 ],
950950 callback: async (_, name) => {
951951 if (typeof name !== 'string' || !name) {
952952 throw new Error('The unnamed argument must be a non-empty string.');
953953 }
public/style.css+8 -2
@@ -1040,8 +1040,14 @@ body .panelControlBar {
10401040 height: var(--swipeCounterHeight);
10411041}
10421042
10431043body:not(.swipeAllMessages) .mes:not(.last_mes) .swipes-counter {
10441044 opacityvisibility: 0.3hidden;
1045+}
1046+
1047+body.swipeAllMessages .mes:not(.last_mes) .swipes-counter {
1048+ /* Avoid expensive DOM queries */
1049+ opacity: 0.3 !important;
1050+ display: flex !important;
10451051}
10461052
10471053.swipe_left {
src/endpoints/backends/chat-completions.js+1 -1
@@ -280,7 +280,7 @@ async function sendMakerSuiteRequest(request, response) {
280280 delete generationConfig.stopSequences;
281281 }
282282
283283 const should_use_system_prompt = (model.includes('gemini-1.5-flash') || model.includes('gemini-1.5-pro') || model.includes('gemini-exp-1114') || model.includesstartsWith('gemini-exp-1121')) && request.body.use_makersuite_sysprompt;
284284 const prompt = convertGooglePrompt(request.body.messages, model, should_use_system_prompt, request.body.char_name, request.body.user_name);
285285 let body = {
286286 contents: prompt.contents,
src/endpoints/characters.js+4 -3
@@ -14,7 +14,7 @@ import jimp from 'jimp';
1414
1515import { AVATAR_WIDTH, AVATAR_HEIGHT } from '../constants.js';
1616import { jsonParser, urlencodedParser } from '../express-common.js';
1717import { deepMerge, humanizedISO8601DateTime, tryParse, extractFileFromZipBuffer, MemoryLimitedMap, getConfigValue } from '../util.js';
1818import { TavernCardValidator } from '../validator/TavernCardValidator.js';
1919import { parse, write } from '../character-card-parser.js';
2020import { readWorldInfoFile } from './worldinfo.js';
@@ -23,8 +23,9 @@ import { importRisuSprites } from './sprites.js';
2323const defaultAvatarPath = './public/img/ai4.png';
2424
2525// KV-store for parsed character data
26-// 100 MB limit. Would take roughly 3000 characters to reach this limit
26+const cacheCapacity = Number(getConfigValue('cardsCacheCapacity', 100)); // MB
27-const characterDataCache = new MemoryLimitedMap(1024 * 1024 * 100);
27+// With 100 MB limit it would take roughly 3000 characters to reach this limit
28+const characterDataCache = new MemoryLimitedMap(1024 * 1024 * cacheCapacity);
2829// Some Android devices require tighter memory management
2930const isAndroid = process.platform === 'android';
3031
src/endpoints/chats.js+4 -4
@@ -203,19 +203,19 @@ function importKoboldLiteChat(_userName, _characterName, data) {
203203
204204 /** @type {function(string): object} */
205205 function processKoboldMessage(msg) {
206206 const isUser = msg.includes(inputToken) || msg.includes(outputToken);
207207 return {
208208 name: isUser ? header.user_name : header.character_name,
209209 is_user: isUser,
210210 mes: msg.replacereplaceAll(inputToken, '').replacereplaceAll(outputToken, '').trim(),
211211 send_date: Date.now(),
212212 };
213213 }
214214
215215 // Create the header
216216 const header = {
217217 user_name: String(data.savedsettings.chatname),
218218 character_name: String(data.savedsettings.chatopponent).split('||$||')[0],
219219 };
220220 // Format messages
221221 const formattedMessages = data.actions.map(processKoboldMessage);
src/endpoints/users-public.js+8 -8
@@ -67,17 +67,17 @@ router.post('/login', jsonParser, async (request, response) => {
6767 const user = await storage.getItem(toKey(request.body.handle));
6868
6969 if (!user) {
7070 console.log('Login failed: User', request.body.handle, 'not found');
7171 return response.status(403).json({ error: 'Incorrect credentials' });
7272 }
7373
7474 if (!user.enabled) {
7575 console.log('Login failed: User', user.handle, 'is disabled');
7676 return response.status(403).json({ error: 'User is disabled' });
7777 }
7878
7979 if (user.password && user.password !== getPasswordHash(request.body.password, user.salt)) {
8080 console.log('Login failed: Incorrect password for', user.handle);
8181 return response.status(403).json({ error: 'Incorrect credentials' });
8282 }
8383
@@ -88,7 +88,7 @@ router.post('/login', jsonParser, async (request, response) => {
8888
8989 await loginLimiter.delete(ip);
9090 request.session.handle = user.handle;
9191 console.log('Login successful:', user.handle, request'from', ip, 'at', new Date().sessiontoLocaleString());
9292 return response.json({ handle: user.handle });
9393 } catch (error) {
9494 if (error instanceof RateLimiterRes) {
@@ -115,12 +115,12 @@ router.post('/recover-step1', jsonParser, async (request, response) => {
115115 const user = await storage.getItem(toKey(request.body.handle));
116116
117117 if (!user) {
118118 console.log('Recover step 1 failed: User', request.body.handle, 'not found');
119119 return response.status(404).json({ error: 'User not found' });
120120 }
121121
122122 if (!user.enabled) {
123123 console.log('Recover step 1 failed: User', user.handle, 'is disabled');
124124 return response.status(403).json({ error: 'User is disabled' });
125125 }
126126
@@ -153,12 +153,12 @@ router.post('/recover-step2', jsonParser, async (request, response) => {
153153 const ip = getIpFromRequest(request);
154154
155155 if (!user) {
156156 console.log('Recover step 2 failed: User', request.body.handle, 'not found');
157157 return response.status(404).json({ error: 'User not found' });
158158 }
159159
160160 if (!user.enabled) {
161161 console.log('Recover step 2 failed: User', user.handle, 'is disabled');
162162 return response.status(403).json({ error: 'User is disabled' });
163163 }
164164
src/prompt-converters.js+1 -0
@@ -347,6 +347,7 @@ export function convertGooglePrompt(messages, model, useSysPrompt = false, charN
347347 'gemini-1.5-flash-8b-exp-0924',
348348 'gemini-exp-1114',
349349 'gemini-exp-1121',
350+ 'gemini-exp-1206',
350351 'gemini-1.5-pro',
351352 'gemini-1.5-pro-latest',
352353 'gemini-1.5-pro-001',
src/util.js+3 -2
@@ -680,8 +680,9 @@ export class MemoryLimitedMap {
680680 * @param {number} maxMemoryInBytes - The maximum allowed memory in bytes for string values.
681681 */
682682 constructor(maxMemoryInBytes) {
683683 if (typeof maxMemoryInBytes !== 'number' || maxMemoryInBytes <= 0 || isNaN(maxMemoryInBytes)) {
684- throw new Error('maxMemoryInBytes must be a positive number');
684+ console.warn('Invalid maxMemoryInBytes, using a fallback value of 1 GB.');
685+ maxMemoryInBytes = 1024 * 1024 * 1024; // 1 GB
685686 }
686687 this.maxMemory = maxMemoryInBytes;
687688 this.currentMemory = 0;