Merge branch 'staging' into persona-lorebook

84d2530c52d6d7ddfa6ddd80f3300111e8f433ee

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

30 files changed, +437 -235Ignore whitespace
.eslintrc.cjs+1 -0
@@ -54,6 +54,7 @@ module.exports = {
54 },54 },
55 // These scripts are loaded in HTML; tell ESLint not to complain about them being undefined55 // These scripts are loaded in HTML; tell ESLint not to complain about them being undefined
56 globals: {56 globals: {
57 globalThis: 'readonly',
57 ePub: 'readonly',58 ePub: 'readonly',
58 pdfjsLib: 'readonly',59 pdfjsLib: 'readonly',
59 toastr: 'readonly',60 toastr: 'readonly',
default/config.yaml+2 -0
@@ -1,6 +1,8 @@
1# -- DATA CONFIGURATION --1# -- DATA CONFIGURATION --
2# Root directory for user data storage2# Root directory for user data storage
3dataRoot: ./data3dataRoot: ./data
4# The maximum amount of memory that parsed character cards can use in MB
5cardsCacheCapacity: 100
4# -- SERVER CONFIGURATION --6# -- SERVER CONFIGURATION --
5# Listen for incoming connections7# Listen for incoming connections
6listen: false8listen: false
package-lock.json+13 -9
@@ -1,12 +1,12 @@
1{1{
2 "name": "sillytavern",2 "name": "sillytavern",
3 "version": "1.12.8",3 "version": "1.12.9",
4 "lockfileVersion": 3,4 "lockfileVersion": 3,
5 "requires": true,5 "requires": true,
6 "packages": {6 "packages": {
7 "": {7 "": {
8 "name": "sillytavern",8 "name": "sillytavern",
9 "version": "1.12.8",9 "version": "1.12.9",
10 "hasInstallScript": true,10 "hasInstallScript": true,
11 "license": "AGPL-3.0",11 "license": "AGPL-3.0",
12 "dependencies": {12 "dependencies": {
@@ -3591,9 +3591,9 @@
3591 "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="3591 "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="
3592 },3592 },
3593 "node_modules/express": {3593 "node_modules/express": {
3594 "version": "4.21.1",3594 "version": "4.21.2",
3595 "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",3595 "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
3596 "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",3596 "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
3597 "license": "MIT",3597 "license": "MIT",
3598 "dependencies": {3598 "dependencies": {
3599 "accepts": "~1.3.8",3599 "accepts": "~1.3.8",
@@ -3615,7 +3615,7 @@
3615 "methods": "~1.1.2",3615 "methods": "~1.1.2",
3616 "on-finished": "2.4.1",3616 "on-finished": "2.4.1",
3617 "parseurl": "~1.3.3",3617 "parseurl": "~1.3.3",
3618 "path-to-regexp": "0.1.10",3618 "path-to-regexp": "0.1.12",
3619 "proxy-addr": "~2.0.7",3619 "proxy-addr": "~2.0.7",
3620 "qs": "6.13.0",3620 "qs": "6.13.0",
3621 "range-parser": "~1.2.1",3621 "range-parser": "~1.2.1",
@@ -3630,6 +3630,10 @@
3630 },3630 },
3631 "engines": {3631 "engines": {
3632 "node": ">= 0.10.0"3632 "node": ">= 0.10.0"
3633 },
3634 "funding": {
3635 "type": "opencollective",
3636 "url": "https://opencollective.com/express"
3633 }3637 }
3634 },3638 },
3635 "node_modules/express/node_modules/cookie": {3639 "node_modules/express/node_modules/cookie": {
@@ -5725,9 +5729,9 @@
5725 }5729 }
5726 },5730 },
5727 "node_modules/path-to-regexp": {5731 "node_modules/path-to-regexp": {
5728 "version": "0.1.10",5732 "version": "0.1.12",
5729 "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",5733 "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
5730 "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==",5734 "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
5731 "license": "MIT"5735 "license": "MIT"
5732 },5736 },
5733 "node_modules/peek-readable": {5737 "node_modules/peek-readable": {
package.json+1 -1
@@ -84,7 +84,7 @@
84 "type": "git",84 "type": "git",
85 "url": "https://github.com/SillyTavern/SillyTavern.git"85 "url": "https://github.com/SillyTavern/SillyTavern.git"
86 },86 },
87 "version": "1.12.8",87 "version": "1.12.9",
88 "scripts": {88 "scripts": {
89 "start": "node server.js",89 "start": "node server.js",
90 "start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js",90 "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 @@
1import libs from './lib';1import libs from './lib';
2import getContext from './scripts/st-context';
23
3// Global namespace modules4// Global namespace modules
4declare var ai;5declare var ai;
@@ -6,7 +7,7 @@ declare var pdfjsLib;
6declare var ePub;7declare var ePub;
78
8declare var SillyTavern: {9declare var SillyTavern: {
9 getContext(): any;10 getContext(): typeof getContext;
10 llm: any;11 llm: any;
11 libs: typeof libs;12 libs: typeof libs;
12};13};
public/index.html+2 -1
@@ -2987,6 +2987,7 @@
2987 <optgroup label="Subversions">2987 <optgroup label="Subversions">
2988 <option value="gemini-exp-1121">Gemini Experimental 2024-11-21</option>2988 <option value="gemini-exp-1121">Gemini Experimental 2024-11-21</option>
2989 <option value="gemini-exp-1114">Gemini Experimental 2024-11-14</option>2989 <option value="gemini-exp-1114">Gemini Experimental 2024-11-14</option>
2990 <option value="gemini-exp-1206">Gemini Experimental 2024-12-06</option>
2990 <option value="gemini-1.5-pro-exp-0801">Gemini 1.5 Pro Experiment 2024-08-01</option>2991 <option value="gemini-1.5-pro-exp-0801">Gemini 1.5 Pro Experiment 2024-08-01</option>
2991 <option value="gemini-1.5-pro-exp-0827">Gemini 1.5 Pro Experiment 2024-08-27</option>2992 <option value="gemini-1.5-pro-exp-0827">Gemini 1.5 Pro Experiment 2024-08-27</option>
2992 <option value="gemini-1.5-pro-latest">Gemini 1.5 Pro [latest]</option>2993 <option value="gemini-1.5-pro-latest">Gemini 1.5 Pro [latest]</option>
@@ -4183,7 +4184,7 @@
4183 </label>4184 </label>
4184 <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.">4185 <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.">
4185 <input id="show_swipe_num_all_messages" type="checkbox" />4186 <input id="show_swipe_num_all_messages" type="checkbox" />
4186 <small data-i18n="Swipe # for All Messages">Swipe # for All Messages</small><i class="fa-solid fa-mobile-screen-button"></i>4187 <small data-i18n="Swipe # for All Messages">Swipe # for All Messages</small>
4187 </label>4188 </label>
4188 <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">4189 <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">
4189 <input id="hotswapEnabled" type="checkbox" />4190 <input id="hotswapEnabled" type="checkbox" />
public/jsconfig.json+2 -1
@@ -5,7 +5,8 @@
5 "module": "ESNext",5 "module": "ESNext",
6 "moduleResolution": "node",6 "moduleResolution": "node",
7 "allowUmdGlobalAccess": true,7 "allowUmdGlobalAccess": true,
8 "allowSyntheticDefaultImports": true8 "allowSyntheticDefaultImports": true,
9 "strictBindCallApply": true
9 },10 },
10 "exclude": [11 "exclude": [
11 "**/node_modules/**",12 "**/node_modules/**",
public/script.js+42 -138
@@ -13,7 +13,7 @@ import {
13 default as libs,13 default as libs,
14} from './lib.js';14} from './lib.js';
1515
16import { humanizedDateTime, favsToHotswap, getMessageTimeStamp, dragElement, isMobile, initRossMods, shouldSendOnEnter } from './scripts/RossAscends-mods.js';16import { humanizedDateTime, favsToHotswap, getMessageTimeStamp, dragElement, isMobile, initRossMods } from './scripts/RossAscends-mods.js';
17import { userStatsHandler, statMesProcess, initStats } from './scripts/stats.js';17import { userStatsHandler, statMesProcess, initStats } from './scripts/stats.js';
18import {18import {
19 generateKoboldWithStreaming,19 generateKoboldWithStreaming,
@@ -37,8 +37,6 @@ import {
37 parseTabbyLogprobs,37 parseTabbyLogprobs,
38} from './scripts/textgen-settings.js';38} from './scripts/textgen-settings.js';
3939
40const { MANCER, TOGETHERAI, OOBA, VLLM, APHRODITE, TABBY, OLLAMA, INFERMATICAI, DREAMGEN, OPENROUTER, FEATHERLESS } = textgen_types;
41
42import {40import {
43 world_info,41 world_info,
44 getWorldInfoPrompt,42 getWorldInfoPrompt,
@@ -67,9 +65,7 @@ import {
67 getGroupChat,65 getGroupChat,
68 renameGroupMember,66 renameGroupMember,
69 createNewGroupChat,67 createNewGroupChat,
70 getGroupPastChats,
71 getGroupAvatar,68 getGroupAvatar,
72 openGroupChat,
73 editGroup,69 editGroup,
74 deleteGroupChat,70 deleteGroupChat,
75 renameGroupChat,71 renameGroupChat,
@@ -98,7 +94,6 @@ import {
98 resetMovableStyles,94 resetMovableStyles,
99 forceCharacterEditorTokenize,95 forceCharacterEditorTokenize,
100 applyPowerUserSettings,96 applyPowerUserSettings,
101 switchSwipeNumAllMessages,
102} from './scripts/power-user.js';97} from './scripts/power-user.js';
10398
104import {99import {
@@ -174,8 +169,8 @@ import {
174} from './scripts/utils.js';169} from './scripts/utils.js';
175import { debounce_timeout } from './scripts/constants.js';170import { debounce_timeout } from './scripts/constants.js';
176171
177import { ModuleWorkerWrapper, doDailyExtensionUpdatesCheck, extension_settings, getContext, initExtensions, loadExtensionSettings, renderExtensionTemplate, renderExtensionTemplateAsync, runGenerationInterceptors, saveMetadataDebounced, writeExtensionField } from './scripts/extensions.js';172import { doDailyExtensionUpdatesCheck, extension_settings, initExtensions, loadExtensionSettings, runGenerationInterceptors, saveMetadataDebounced } from './scripts/extensions.js';
178import { COMMENT_NAME_DEFAULT, executeSlashCommands, executeSlashCommandsOnChatInput, executeSlashCommandsWithOptions, getSlashCommandsHelp, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, processChatSlashCommands, registerSlashCommand, stopScriptExecution } from './scripts/slash-commands.js';173import { COMMENT_NAME_DEFAULT, executeSlashCommandsOnChatInput, getSlashCommandsHelp, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, processChatSlashCommands, stopScriptExecution } from './scripts/slash-commands.js';
179import {174import {
180 tag_map,175 tag_map,
181 tags,176 tags,
@@ -225,8 +220,8 @@ import {
225 instruct_presets,220 instruct_presets,
226 selectContextPreset,221 selectContextPreset,
227} from './scripts/instruct-mode.js';222} from './scripts/instruct-mode.js';
228import { initLocales, t, translate } from './scripts/i18n.js';223import { initLocales, t } from './scripts/i18n.js';
229import { getFriendlyTokenizerName, getTokenCount, getTokenCountAsync, getTokenizerModel, initTokenizers, saveTokenCache, TOKENIZER_SUPPORTED_KEY } from './scripts/tokenizers.js';224import { getFriendlyTokenizerName, getTokenCount, getTokenCountAsync, initTokenizers, saveTokenCache, TOKENIZER_SUPPORTED_KEY } from './scripts/tokenizers.js';
230import {225import {
231 user_avatar,226 user_avatar,
232 getUserAvatars,227 getUserAvatars,
@@ -242,11 +237,11 @@ import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay
242import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels, loadTabbyModels } from './scripts/textgen-models.js';237import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels, loadTabbyModels } from './scripts/textgen-models.js';
243import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId, preserveNeutralChat, restoreNeutralChat } from './scripts/chats.js';238import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId, preserveNeutralChat, restoreNeutralChat } from './scripts/chats.js';
244import { getPresetManager, initPresetManager } from './scripts/preset-manager.js';239import { getPresetManager, initPresetManager } from './scripts/preset-manager.js';
245import { MacrosParser, evaluateMacros, getLastMessageId, initMacros } from './scripts/macros.js';240import { evaluateMacros, getLastMessageId, initMacros } from './scripts/macros.js';
246import { currentUser, setUserControls } from './scripts/user.js';241import { currentUser, setUserControls } from './scripts/user.js';
247import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js';242import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js';
248import { renderTemplate, renderTemplateAsync } from './scripts/templates.js';243import { renderTemplate, renderTemplateAsync } from './scripts/templates.js';
249import { initScrapers, ScraperManager } from './scripts/scrapers.js';244import { initScrapers } from './scripts/scrapers.js';
250import { SlashCommandParser } from './scripts/slash-commands/SlashCommandParser.js';245import { SlashCommandParser } from './scripts/slash-commands/SlashCommandParser.js';
251import { SlashCommand } from './scripts/slash-commands/SlashCommand.js';246import { SlashCommand } from './scripts/slash-commands/SlashCommand.js';
252import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './scripts/slash-commands/SlashCommandArgument.js';247import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './scripts/slash-commands/SlashCommandArgument.js';
@@ -268,6 +263,13 @@ import { initServerHistory } from './scripts/server-history.js';
268import { initSettingsSearch } from './scripts/setting-search.js';263import { initSettingsSearch } from './scripts/setting-search.js';
269import { initBulkEdit } from './scripts/bulk-edit.js';264import { initBulkEdit } from './scripts/bulk-edit.js';
270import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js';265import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js';
266import { getContext } from './scripts/st-context.js';
267
268// API OBJECT FOR EXTERNAL WIRING
269globalThis.SillyTavern = {
270 libs,
271 getContext,
272};
271273
272//exporting functions and vars for mods274//exporting functions and vars for mods
273export {275export {
@@ -427,10 +429,6 @@ DOMPurify.addHook('uponSanitizeElement', (node, _, config) => {
427 }429 }
428});430});
429431
430// API OBJECT FOR EXTERNAL WIRING
431window['SillyTavern'] = {};
432window['SillyTavern'].libs = libs;
433
434// Event source init432// Event source init
435export const event_types = {433export const event_types = {
436 APP_READY: 'app_ready',434 APP_READY: 'app_ready',
@@ -536,7 +534,7 @@ let displayVersion = 'SillyTavern';
536534
537let generatedPromptCache = '';535let generatedPromptCache = '';
538let generation_started = new Date();536let generation_started = new Date();
539/** @type {import('scripts/char-data.js').v1CharData[]} */537/** @type {import('./scripts/char-data.js').v1CharData[]} */
540export let characters = [];538export let characters = [];
541export let this_chid;539export let this_chid;
542let saveCharactersPage = 0;540let saveCharactersPage = 0;
@@ -812,7 +810,7 @@ export let menu_type = '';
812export let selected_button = ''; //which button pressed810export let selected_button = ''; //which button pressed
813811
814//create pole save812//create pole save
815let create_save = {813export let create_save = {
816 name: '',814 name: '',
817 description: '',815 description: '',
818 creator_notes: '',816 creator_notes: '',
@@ -864,7 +862,7 @@ export let amount_gen = 80; //default max length of AI generated responses
864export let max_context = 2048;862export let max_context = 2048;
865863
866var swipes = true;864var swipes = true;
867let extension_prompts = {};865export let extension_prompts = {};
868866
869export let main_api;// = "kobold";867export let main_api;// = "kobold";
870//novel settings868//novel settings
@@ -1175,7 +1173,7 @@ async function getStatusTextgen() {
1175 return resultCheckStatus();1173 return resultCheckStatus();
1176 }1174 }
11771175
1178 if (textgen_settings.type == OOBA && textgen_settings.bypass_status_check) {1176 if (textgen_settings.type == textgen_types.OOBA && textgen_settings.bypass_status_check) {
1179 setOnlineStatus('Status check bypassed');1177 setOnlineStatus('Status check bypassed');
1180 return resultCheckStatus();1178 return resultCheckStatus();
1181 }1179 }
@@ -1193,34 +1191,34 @@ async function getStatusTextgen() {
11931191
1194 const data = await response.json();1192 const data = await response.json();
11951193
1196 if (textgen_settings.type === MANCER) {1194 if (textgen_settings.type === textgen_types.MANCER) {
1197 loadMancerModels(data?.data);1195 loadMancerModels(data?.data);
1198 setOnlineStatus(textgen_settings.mancer_model);1196 setOnlineStatus(textgen_settings.mancer_model);
1199 } else if (textgen_settings.type === TOGETHERAI) {1197 } else if (textgen_settings.type === textgen_types.TOGETHERAI) {
1200 loadTogetherAIModels(data?.data);1198 loadTogetherAIModels(data?.data);
1201 setOnlineStatus(textgen_settings.togetherai_model);1199 setOnlineStatus(textgen_settings.togetherai_model);
1202 } else if (textgen_settings.type === OLLAMA) {1200 } else if (textgen_settings.type === textgen_types.OLLAMA) {
1203 loadOllamaModels(data?.data);1201 loadOllamaModels(data?.data);
1204 setOnlineStatus(textgen_settings.ollama_model || 'Connected');1202 setOnlineStatus(textgen_settings.ollama_model || 'Connected');
1205 } else if (textgen_settings.type === INFERMATICAI) {1203 } else if (textgen_settings.type === textgen_types.INFERMATICAI) {
1206 loadInfermaticAIModels(data?.data);1204 loadInfermaticAIModels(data?.data);
1207 setOnlineStatus(textgen_settings.infermaticai_model);1205 setOnlineStatus(textgen_settings.infermaticai_model);
1208 } else if (textgen_settings.type === DREAMGEN) {1206 } else if (textgen_settings.type === textgen_types.DREAMGEN) {
1209 loadDreamGenModels(data?.data);1207 loadDreamGenModels(data?.data);
1210 setOnlineStatus(textgen_settings.dreamgen_model);1208 setOnlineStatus(textgen_settings.dreamgen_model);
1211 } else if (textgen_settings.type === OPENROUTER) {1209 } else if (textgen_settings.type === textgen_types.OPENROUTER) {
1212 loadOpenRouterModels(data?.data);1210 loadOpenRouterModels(data?.data);
1213 setOnlineStatus(textgen_settings.openrouter_model);1211 setOnlineStatus(textgen_settings.openrouter_model);
1214 } else if (textgen_settings.type === VLLM) {1212 } else if (textgen_settings.type === textgen_types.VLLM) {
1215 loadVllmModels(data?.data);1213 loadVllmModels(data?.data);
1216 setOnlineStatus(textgen_settings.vllm_model);1214 setOnlineStatus(textgen_settings.vllm_model);
1217 } else if (textgen_settings.type === APHRODITE) {1215 } else if (textgen_settings.type === textgen_types.APHRODITE) {
1218 loadAphroditeModels(data?.data);1216 loadAphroditeModels(data?.data);
1219 setOnlineStatus(textgen_settings.aphrodite_model);1217 setOnlineStatus(textgen_settings.aphrodite_model);
1220 } else if (textgen_settings.type === FEATHERLESS) {1218 } else if (textgen_settings.type === textgen_types.FEATHERLESS) {
1221 loadFeatherlessModels(data?.data);1219 loadFeatherlessModels(data?.data);
1222 setOnlineStatus(textgen_settings.featherless_model);1220 setOnlineStatus(textgen_settings.featherless_model);
1223 } else if (textgen_settings.type === TABBY) {1221 } else if (textgen_settings.type === textgen_types.TABBY) {
1224 loadTabbyModels(data?.data);1222 loadTabbyModels(data?.data);
1225 setOnlineStatus(textgen_settings.tabby_model || data?.result);1223 setOnlineStatus(textgen_settings.tabby_model || data?.result);
1226 } else {1224 } else {
@@ -1637,7 +1635,7 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) {
1637 subEntities = entitiesFilter.applyFilters(subEntities, { clearScoreCache: false, tempOverrides: { [FILTER_TYPES.FOLDER]: FILTER_STATES.UNDEFINED }, clearFuzzySearchCaches: false });1635 subEntities = entitiesFilter.applyFilters(subEntities, { clearScoreCache: false, tempOverrides: { [FILTER_TYPES.FOLDER]: FILTER_STATES.UNDEFINED }, clearFuzzySearchCaches: false });
1638 }1636 }
1639 if (doSort) {1637 if (doSort) {
1640 sortEntitiesList(subEntities);1638 sortEntitiesList(subEntities, false);
1641 }1639 }
1642 entity.entities = subEntities;1640 entity.entities = subEntities;
1643 entity.hidden = subCount - subEntities.length;1641 entity.hidden = subCount - subEntities.length;
@@ -1665,7 +1663,7 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) {
16651663
1666 // Sort before returning if requested1664 // Sort before returning if requested
1667 if (doSort) {1665 if (doSort) {
1668 sortEntitiesList(entities);1666 sortEntitiesList(entities, false);
1669 }1667 }
1670 entitiesFilter.clearFuzzySearchCaches();1668 entitiesFilter.clearFuzzySearchCaches();
1671 return entities;1669 return entities;
@@ -2607,15 +2605,18 @@ export function substituteParams(content, _name1, _name2, _original, _group, _re
2607 };2605 };
2608 }2606 }
26092607
2610 const getGroupValue = () => {2608 const getGroupValue = (includeMuted) => {
2611 if (typeof _group === 'string') {2609 if (typeof _group === 'string') {
2612 return _group;2610 return _group;
2613 }2611 }
26142612
2615 if (selected_group) {2613 if (selected_group) {
2616 const members = groups.find(x => x.id === selected_group)?.members;2614 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);
2617 const names = Array.isArray(members)2618 const names = Array.isArray(members)
2618 ? members.map(m => characters.find(c => c.avatar === m)?.name).filter(Boolean).join(', ')2619 ? members.filter(isMuted).map(m => characters.find(c => c.avatar === m)?.name).filter(Boolean).join(', ')
2619 : '';2620 : '';
2620 return names;2621 return names;
2621 } else {2622 } else {
@@ -2639,7 +2640,8 @@ export function substituteParams(content, _name1, _name2, _original, _group, _re
2639 // Must be substituted last so that they're replaced inside {{description}}2640 // Must be substituted last so that they're replaced inside {{description}}
2640 environment.user = _name1 ?? name1;2641 environment.user = _name1 ?? name1;
2641 environment.char = _name2 ?? name2;2642 environment.char = _name2 ?? name2;
2642 environment.group = environment.charIfNotGroup = getGroupValue();2643 environment.group = environment.charIfNotGroup = getGroupValue(true);
2644 environment.groupNotMuted = getGroupValue(false);
2643 environment.model = getGeneratingModel();2645 environment.model = getGeneratingModel();
26442646
2645 if (additionalMacro && typeof additionalMacro === 'object') {2647 if (additionalMacro && typeof additionalMacro === 'object') {
@@ -5538,7 +5540,7 @@ function extractMultiSwipes(data, type) {
5538 return swipes;5540 return swipes;
5539 }5541 }
55405542
5541 if (main_api === 'openai' || (main_api === 'textgenerationwebui' && [MANCER, VLLM, APHRODITE, TABBY, INFERMATICAI].includes(textgen_settings.type))) {5543 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))) {
5542 if (!Array.isArray(data.choices)) {5544 if (!Array.isArray(data.choices)) {
5543 return swipes;5545 return swipes;
5544 }5546 }
@@ -5701,7 +5703,7 @@ export function cleanUpMessage(getMessage, isImpersonate, isContinue, displayInc
5701 return getMessage;5703 return getMessage;
5702}5704}
57035705
5704async function saveReply(type, getMessage, fromStreaming, title, swipes) {5706export async function saveReply(type, getMessage, fromStreaming, title, swipes) {
5705 if (type != 'append' && type != 'continue' && type != 'appendFinal' && chat.length && (chat[chat.length - 1]['swipe_id'] === undefined ||5707 if (type != 'append' && type != 'continue' && type != 'appendFinal' && chat.length && (chat[chat.length - 1]['swipe_id'] === undefined ||
5706 chat[chat.length - 1]['is_user'])) {5708 chat[chat.length - 1]['is_user'])) {
5707 type = 'normal';5709 type = 'normal';
@@ -7660,14 +7662,12 @@ export function showSwipeButtons() {
7660 //allows for writing individual swipe counters for past messages7662 //allows for writing individual swipe counters for past messages
7661 const lastSwipeCounter = $('.last_mes .swipes-counter');7663 const lastSwipeCounter = $('.last_mes .swipes-counter');
7662 lastSwipeCounter.text(swipeCounterText).show();7664 lastSwipeCounter.text(swipeCounterText).show();
7663
7664 switchSwipeNumAllMessages();
7665}7665}
76667666
7667export function hideSwipeButtons() {7667export function hideSwipeButtons() {
7668 $('#chat').find('.swipe_right').hide();7668 chatElement.find('.swipe_right').hide();
7669 $('#chat').find('.last_mes .swipes-counter').hide();7669 chatElement.find('.last_mes .swipes-counter').hide();
7670 $('#chat').find('.swipe_left').hide();7670 chatElement.find('.swipe_left').hide();
7671}7671}
76727672
7673/**7673/**
@@ -8233,102 +8233,6 @@ async function createOrEditCharacter(e) {
8233 }8233 }
8234}8234}
82358235
8236window['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
8332/**8236/**
8333 * Formats a counter for a swipe view.8237 * Formats a counter for a swipe view.
8334 * @param {number} current The current number of items.8238 * @param {number} current The current number of items.
public/scripts/char-data.js+1 -0
@@ -75,6 +75,7 @@
75 * @property {string} [source_url] - The source URL associated with the character.75 * @property {string} [source_url] - The source URL associated with the character.
76 * @property {{full_path: string}} [chub] - The Chub-specific data associated with the character.76 * @property {{full_path: string}} [chub] - The Chub-specific data associated with the character.
77 * @property {{source: string[]}} [risuai] - The RisuAI-specific data associated with the character.77 * @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.
78 */79 */
7980
80/**81/**
public/scripts/extensions.js+1 -1
@@ -5,6 +5,7 @@ import { showLoader } from './loader.js';
5import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';5import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
6import { renderTemplate, renderTemplateAsync } from './templates.js';6import { renderTemplate, renderTemplateAsync } from './templates.js';
7import { isSubsetOf, setValueByPath } from './utils.js';7import { isSubsetOf, setValueByPath } from './utils.js';
8import { getContext } from './st-context.js';
8export {9export {
9 getContext,10 getContext,
10 getApiUrl,11 getApiUrl,
@@ -174,7 +175,6 @@ const extension_settings = {
174let modules = [];175let modules = [];
175let activeExtensions = new Set();176let activeExtensions = new Set();
176177
177const getContext = () => window['SillyTavern'].getContext();
178const getApiUrl = () => extension_settings.apiUrl;178const getApiUrl = () => extension_settings.apiUrl;
179let connectedToApi = false;179let connectedToApi = false;
180180
public/scripts/extensions/caption/settings.html+1 -0
@@ -62,6 +62,7 @@
62 <option data-type="google" value="gemini-1.5-flash-8b-exp-0924">gemini-1.5-flash-8b-exp-0924</option>62 <option data-type="google" value="gemini-1.5-flash-8b-exp-0924">gemini-1.5-flash-8b-exp-0924</option>
63 <option data-type="google" value="gemini-exp-1114">gemini-exp-1114</option>63 <option data-type="google" value="gemini-exp-1114">gemini-exp-1114</option>
64 <option data-type="google" value="gemini-exp-1121">gemini-exp-1121</option>64 <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>
65 <option data-type="google" value="gemini-1.5-pro">gemini-1.5-pro</option>66 <option data-type="google" value="gemini-1.5-pro">gemini-1.5-pro</option>
66 <option data-type="google" value="gemini-1.5-pro-latest">gemini-1.5-pro-latest</option>67 <option data-type="google" value="gemini-1.5-pro-latest">gemini-1.5-pro-latest</option>
67 <option data-type="google" value="gemini-1.5-pro-001">gemini-1.5-pro-001</option>68 <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';
60export { MODULE_NAME };60export { MODULE_NAME };
6161
62const MODULE_NAME = 'sd';62const MODULE_NAME = 'sd';
63const UPDATE_INTERVAL = 1000;
64// This is a 1x1 transparent PNG63// This is a 1x1 transparent PNG
65const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';64const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
66const CUSTOM_STOP_EVENT = 'sd_stop_generation';65const CUSTOM_STOP_EVENT = 'sd_stop_generation';
@@ -3687,8 +3686,6 @@ async function addSDGenButtons() {
3687 const button = $('#sd_gen');3686 const button = $('#sd_gen');
3688 const dropdown = $('#sd_dropdown');3687 const dropdown = $('#sd_dropdown');
3689 dropdown.hide();3688 dropdown.hide();
3690 button.hide();
3691 messageButton.hide();
36923689
3693 let popper = Popper.createPopper(button.get(0), dropdown.get(0), {3690 let popper = Popper.createPopper(button.get(0), dropdown.get(0), {
3694 placement: 'top',3691 placement: 'top',
@@ -3770,18 +3767,6 @@ function isValidState() {
3770 }3767 }
3771}3768}
37723769
3773async 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
3784setInterval(moduleWorker, UPDATE_INTERVAL);
3785let buttonAbortController = null;3770let buttonAbortController = null;
37863771
3787async function sdMessageButton(e) {3772async function sdMessageButton(e) {
public/scripts/extensions/stable-diffusion/style.css+1 -1
@@ -4,7 +4,7 @@
44
5#sd_dropdown {5#sd_dropdown {
6 z-index: 30000;6 z-index: 30000;
7 backdrop-filter: blur(--SmartThemeBlurStrength);7 backdrop-filter: blur(var(--SmartThemeBlurStrength));
8}8}
99
10#sd_comfy_open_workflow_editor {10#sd_comfy_open_workflow_editor {
public/scripts/extensions/tts/style.css+1 -1
@@ -93,7 +93,7 @@
93.at-settings-separator {93.at-settings-separator {
94 margin-top: 10px;94 margin-top: 10px;
95 margin-bottom: 10px;95 margin-bottom: 10px;
96 padding: 18x;96 padding: 18px;
97 font-weight: bold;97 font-weight: bold;
98 border-top: 1px solid #e1e1e1; /* Grey line */98 border-top: 1px solid #e1e1e1; /* Grey line */
99 border-bottom: 1px solid #e1e1e1; /* Grey line */99 border-bottom: 1px solid #e1e1e1; /* Grey line */
public/scripts/group-chats.js+8 -6
@@ -1265,18 +1265,20 @@ function getGroupCharacters({ doFilter, onlyMembers } = {}) {
1265 const thisGroup = openGroupId && groups.find((x) => x.id == openGroupId);1265 const thisGroup = openGroupId && groups.find((x) => x.id == openGroupId);
1266 let candidates = characters1266 let candidates = characters
1267 .filter((x) => isGroupMember(thisGroup, x.avatar) == onlyMembers)1267 .filter((x) => isGroupMember(thisGroup, x.avatar) == onlyMembers)
1268 .map((x, index) => ({ item: x, id: index, type: 'character' }));1268 .map((x) => ({ item: x, id: characters.indexOf(x), type: 'character' }));
1269
1270 if (doFilter) {
1271 candidates = groupCandidatesFilter.applyFilters(candidates);
1272 }
12691273
1270 if (onlyMembers) {1274 if (onlyMembers) {
1271 candidates.sort(sortMembersFn);1275 candidates.sort(sortMembersFn);
1272 } else {1276 } 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);
1278 }1279 }
12791280
1281 groupCandidatesFilter.clearFuzzySearchCaches();
1280 return candidates;1282 return candidates;
1281}1283}
12821284
public/scripts/openai.js+4 -3
@@ -379,8 +379,8 @@ export let selected_proxy = proxies[0];
379let openai_setting_names;379let openai_setting_names;
380let openai_settings;380let openai_settings;
381381
382382/** @type {import('./PromptManager.js').PromptManager} */
383let promptManager = null;383export let promptManager = null;
384384
385async function validateReverseProxy() {385async function validateReverseProxy() {
386 if (!oai_settings.reverse_proxy) {386 if (!oai_settings.reverse_proxy) {
@@ -4077,7 +4077,7 @@ async function onModelChange() {
4077 $('#openai_max_context').attr('max', max_2mil);4077 $('#openai_max_context').attr('max', max_2mil);
4078 } else if (value.includes('gemini-exp-1114') || value.includes('gemini-exp-1121')) {4078 } else if (value.includes('gemini-exp-1114') || value.includes('gemini-exp-1121')) {
4079 $('#openai_max_context').attr('max', max_32k);4079 $('#openai_max_context').attr('max', max_32k);
4080 } else if (value.includes('gemini-1.5-pro')) {4080 } else if (value.includes('gemini-1.5-pro') || value.includes('gemini-exp-1206')) {
4081 $('#openai_max_context').attr('max', max_2mil);4081 $('#openai_max_context').attr('max', max_2mil);
4082 } else if (value.includes('gemini-1.5-flash')) {4082 } else if (value.includes('gemini-1.5-flash')) {
4083 $('#openai_max_context').attr('max', max_1mil);4083 $('#openai_max_context').attr('max', max_1mil);
@@ -4761,6 +4761,7 @@ export function isImageInliningSupported() {
4761 'gemini-1.5-flash-8b-exp-0924',4761 'gemini-1.5-flash-8b-exp-0924',
4762 'gemini-exp-1114',4762 'gemini-exp-1114',
4763 'gemini-exp-1121',4763 'gemini-exp-1121',
4764 'gemini-exp-1206',
4764 'gemini-1.0-pro-vision-latest',4765 'gemini-1.0-pro-vision-latest',
4765 'gemini-1.5-pro',4766 'gemini-1.5-pro',
4766 'gemini-1.5-pro-latest',4767 'gemini-1.5-pro-latest',
public/scripts/power-user.js+12 -11
@@ -60,7 +60,6 @@ export {
60 loadMovingUIState,60 loadMovingUIState,
61 collapseNewlines,61 collapseNewlines,
62 playMessageSound,62 playMessageSound,
63 sortEntitiesList,
64 fixMarkdown,63 fixMarkdown,
65 power_user,64 power_user,
66 send_on_enter_options,65 send_on_enter_options,
@@ -474,9 +473,9 @@ function switchCompactInputArea() {
474 $('#compact_input_area').prop('checked', power_user.compact_input_area);473 $('#compact_input_area').prop('checked', power_user.compact_input_area);
475}474}
476475
477export function switchSwipeNumAllMessages() {476function switchSwipeNumAllMessages() {
478 $('#show_swipe_num_all_messages').prop('checked', power_user.show_swipe_num_all_messages);477 $('#show_swipe_num_all_messages').prop('checked', power_user.show_swipe_num_all_messages);
479 $('.mes:not(.last_mes) .swipes-counter').css('opacity', '').toggle(power_user.show_swipe_num_all_messages);478 $('body').toggleClass('swipeAllMessages', !!power_user.show_swipe_num_all_messages);
480}479}
481480
482var originalSliderValues = [];481var originalSliderValues = [];
@@ -1838,7 +1837,7 @@ async function loadContextSettings() {
1838 * Common function to perform fuzzy search with optional caching1837 * Common function to perform fuzzy search with optional caching
1839 * @param {string} type - Type of search from fuzzySearchCategories1838 * @param {string} type - Type of search from fuzzySearchCategories
1840 * @param {any[]} data - Data array to search in1839 * @param {any[]} data - Data array to search in
1841 * @param {Array<{name: string, weight: number, getFn?: Function}>} keys - Fuse.js keys configuration1840 * @param {Array<{name: string, weight: number, getFn?: (obj: any) => string}>} keys - Fuse.js keys configuration
1842 * @param {string} searchValue - The search term1841 * @param {string} searchValue - The search term
1843 * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches1842 * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
1844 * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score1843 * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score
@@ -1852,7 +1851,6 @@ function performFuzzySearch(type, data, keys, searchValue, fuzzySearchCaches = n
1852 }1851 }
1853 }1852 }
18541853
1855 // @ts-ignore
1856 const fuse = new Fuse(data, {1854 const fuse = new Fuse(data, {
1857 keys: keys,1855 keys: keys,
1858 includeScore: true,1856 includeScore: true,
@@ -2081,19 +2079,22 @@ const compareFunc = (first, second) => {
2081/**2079/**
2082 * Sorts an array of entities based on the current sort settings2080 * Sorts an array of entities based on the current sort settings
2083 * @param {any[]} entities An array of objects with an `item` property2081 * @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
2084 */2084 */
2085function sortEntitiesList(entities) {2085export function sortEntitiesList(entities, forceSearch, filterHelper = null) {
2086 filterHelper = filterHelper ?? entitiesFilter;
2086 if (power_user.sort_field == undefined || entities.length === 0) {2087 if (power_user.sort_field == undefined || entities.length === 0) {
2087 return;2088 return;
2088 }2089 }
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') {
2091 shuffle(entities);2094 shuffle(entities);
2092 return;2095 return;
2093 }2096 }
20942097
2095 const isSearch = $('#character_sort_order option[data-field="search"]').is(':selected');
2096
2097 entities.sort((a, b) => {2098 entities.sort((a, b) => {
2098 // Sort tags/folders will always be at the top2099 // Sort tags/folders will always be at the top
2099 if (a.type === 'tag' && b.type !== 'tag') {2100 if (a.type === 'tag' && b.type !== 'tag') {
@@ -2105,8 +2106,8 @@ function sortEntitiesList(entities) {
21052106
2106 // If we have search sorting, we take scores and use those2107 // If we have search sorting, we take scores and use those
2107 if (isSearch) {2108 if (isSearch) {
2108 const aScore = entitiesFilter.getScore(FILTER_TYPES.SEARCH, `${a.type}.${a.id}`);2109 const aScore = filterHelper.getScore(FILTER_TYPES.SEARCH, `${a.type}.${a.id}`);
2109 const bScore = entitiesFilter.getScore(FILTER_TYPES.SEARCH, `${b.type}.${b.id}`);2110 const bScore = filterHelper.getScore(FILTER_TYPES.SEARCH, `${b.type}.${b.id}`);
2110 return (aScore - bScore);2111 return (aScore - bScore);
2111 }2112 }
21122113
public/scripts/slash-commands.js+113 -4
@@ -52,7 +52,7 @@ import { hideChatMessageRange } from './chats.js';
52import { getContext, saveMetadataDebounced } from './extensions.js';52import { getContext, saveMetadataDebounced } from './extensions.js';
53import { getRegexedString, regex_placement } from './extensions/regex/engine.js';53import { getRegexedString, regex_placement } from './extensions/regex/engine.js';
54import { findGroupMemberId, groups, is_group_generating, openGroupById, resetSelectedGroup, saveGroupChat, selected_group } from './group-chats.js';54import { findGroupMemberId, groups, is_group_generating, openGroupById, resetSelectedGroup, saveGroupChat, selected_group } from './group-chats.js';
55import { chat_completion_sources, oai_settings, setupChatCompletionPromptManager } from './openai.js';55import { chat_completion_sources, oai_settings, promptManager } from './openai.js';
56import { autoSelectPersona, retriggerFirstMessageOnEmptyChat, setPersonaLockState, togglePersonaLock, user_avatar } from './personas.js';56import { autoSelectPersona, retriggerFirstMessageOnEmptyChat, setPersonaLockState, togglePersonaLock, user_avatar } from './personas.js';
57import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js';57import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js';
58import { SERVER_INPUTS, textgen_types, textgenerationwebui_settings } from './textgen-settings.js';58import { SERVER_INPUTS, textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
@@ -1699,6 +1699,49 @@ export function initDefaultSlashCommands() {
1699 helpString: 'Sets the model for the current API. Gets the current model name if no argument is provided.',1699 helpString: 'Sets the model for the current API. Gets the current model name if no argument is provided.',
1700 }));1700 }));
1701 SlashCommandParser.addCommandObject(SlashCommand.fromProps({1701 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({
1702 name: 'setpromptentry',1745 name: 'setpromptentry',
1703 aliases: ['setpromptentries'],1746 aliases: ['setpromptentries'],
1704 callback: setPromptEntryCallback,1747 callback: setPromptEntryCallback,
@@ -1709,7 +1752,6 @@ export function initDefaultSlashCommands() {
1709 typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST],1752 typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST],
1710 acceptsMultiple: true,1753 acceptsMultiple: true,
1711 enumProvider: () => {1754 enumProvider: () => {
1712 const promptManager = setupChatCompletionPromptManager(oai_settings);
1713 const prompts = promptManager.serviceSettings.prompts;1755 const prompts = promptManager.serviceSettings.prompts;
1714 return prompts.map(prompt => new SlashCommandEnumValue(prompt.identifier, prompt.name, enumTypes.enum));1756 return prompts.map(prompt => new SlashCommandEnumValue(prompt.identifier, prompt.name, enumTypes.enum));
1715 },1757 },
@@ -1720,7 +1762,6 @@ export function initDefaultSlashCommands() {
1720 typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST],1762 typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST],
1721 acceptsMultiple: true,1763 acceptsMultiple: true,
1722 enumProvider: () => {1764 enumProvider: () => {
1723 const promptManager = setupChatCompletionPromptManager(oai_settings);
1724 const prompts = promptManager.serviceSettings.prompts;1765 const prompts = promptManager.serviceSettings.prompts;
1725 return prompts.map(prompt => new SlashCommandEnumValue(prompt.name, prompt.identifier, enumTypes.enum));1766 return prompts.map(prompt => new SlashCommandEnumValue(prompt.name, prompt.identifier, enumTypes.enum));
1726 },1767 },
@@ -3787,6 +3828,75 @@ function modelCallback(args, model) {
3787}3828}
37883829
3789/**3830/**
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 */
3838function 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/**
3790 * Sets state of prompt entries (toggles) either via identifier/uuid or name.3900 * Sets state of prompt entries (toggles) either via identifier/uuid or name.
3791 * @param {object} args Object containing arguments3901 * @param {object} args Object containing arguments
3792 * @param {string} args.identifier Select prompt entry using an identifier (uuid)3902 * @param {string} args.identifier Select prompt entry using an identifier (uuid)
@@ -3796,7 +3906,6 @@ function modelCallback(args, model) {
3796 */3906 */
3797function setPromptEntryCallback(args, targetState) {3907function setPromptEntryCallback(args, targetState) {
3798 // needs promptManager to manipulate prompt entries3908 // needs promptManager to manipulate prompt entries
3799 const promptManager = setupChatCompletionPromptManager(oai_settings);
3800 const prompts = promptManager.serviceSettings.prompts;3909 const prompts = promptManager.serviceSettings.prompts;
38013910
3802 function parseArgs(arg) {3911 function parseArgs(arg) {
public/scripts/st-context.js+171 -0
@@ -0,0 +1,171 @@
1import {
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';
44import {
45 extension_settings,
46 ModuleWorkerWrapper,
47 renderExtensionTemplate,
48 renderExtensionTemplateAsync,
49 writeExtensionField,
50} from './extensions.js';
51import { groups, openGroupChat, selected_group } from './group-chats.js';
52import { t, translate } from './i18n.js';
53import { hideLoader, showLoader } from './loader.js';
54import { MacrosParser } from './macros.js';
55import { oai_settings } from './openai.js';
56import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
57import { power_user, registerDebugFunction } from './power-user.js';
58import { isMobile, shouldSendOnEnter } from './RossAscends-mods.js';
59import { ScraperManager } from './scrapers.js';
60import { executeSlashCommands, executeSlashCommandsWithOptions, registerSlashCommand } from './slash-commands.js';
61import { SlashCommand } from './slash-commands/SlashCommand.js';
62import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
63import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
64import { tag_map, tags } from './tags.js';
65import { textgenerationwebui_settings } from './textgen-settings.js';
66import { getTokenCount, getTokenCountAsync, getTokenizerModel } from './tokenizers.js';
67import { ToolManager } from './tool-calling.js';
68import { timestampToMoment } from './utils.js';
69
70export 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
171export default getContext;
public/scripts/templates/macros.html+2 -1
@@ -21,7 +21,8 @@
21 <li><tt>&lcub;&lcub;user&rcub;&rcub;</tt> – <span data-i18n="help_macros_15">your current Persona username</span></li>21 <li><tt>&lcub;&lcub;user&rcub;&rcub;</tt> – <span data-i18n="help_macros_15">your current Persona username</span></li>
22 <li><tt>&lcub;&lcub;char&rcub;&rcub;</tt> – <span data-i18n="help_macros_16">the Character's name</span></li>22 <li><tt>&lcub;&lcub;char&rcub;&rcub;</tt> – <span data-i18n="help_macros_16">the Character's name</span></li>
23 <li><tt>&lcub;&lcub;char_version&rcub;&rcub;</tt> – <span data-i18n="help_macros_17">the Character's version number</span></li>23 <li><tt>&lcub;&lcub;char_version&rcub;&rcub;</tt> – <span data-i18n="help_macros_17">the Character's version number</span></li>
24 <li><tt>&lcub;&lcub;group&rcub;&rcub;</tt> – <span data-i18n="help_macros_18">a comma-separated list of group member names or the character name in solo chats. Alias: &lcub;&lcub;charIfNotGroup&rcub;&rcub;</span></li>24 <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>
25 <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>26 <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>
26 <li><tt>&lcub;&lcub;lastMessage&rcub;&rcub;</tt> – <span data-i18n="help_macros_20">the text of the latest chat message.</span></li>27 <li><tt>&lcub;&lcub;lastMessage&rcub;&rcub;</tt> – <span data-i18n="help_macros_20">the text of the latest chat message.</span></li>
27 <li><tt>&lcub;&lcub;lastUserMessage&rcub;&rcub;</tt> – <span data-i18n="help_macros_lastUser">the text of the latest user chat message.</span></li>28 <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 }
18import { ENCODE_TOKENIZERS, TEXTGEN_TOKENIZERS, getTextTokens, tokenizers } from './tokenizers.js';18import { ENCODE_TOKENIZERS, TEXTGEN_TOKENIZERS, getTextTokens, tokenizers } from './tokenizers.js';
19import { getSortableDelay, onlyUnique } from './utils.js';19import { getSortableDelay, onlyUnique } from './utils.js';
2020
21export {
22 settings as textgenerationwebui_settings,
23 loadTextGenSettings,
24 generateTextGenWithStreaming,
25 formatTextGenURL,
26};
27
28export const textgen_types = {21export const textgen_types = {
29 OOBA: 'ooba',22 OOBA: 'ooba',
30 MANCER: 'mancer',23 MANCER: 'mancer',
@@ -197,6 +190,10 @@ const settings = {
197 featherless_model: '',190 featherless_model: '',
198};191};
199192
193export {
194 settings as textgenerationwebui_settings,
195};
196
200export let textgenerationwebui_banned_in_macros = [];197export let textgenerationwebui_banned_in_macros = [];
201198
202export let textgenerationwebui_presets = [];199export let textgenerationwebui_presets = [];
@@ -327,7 +324,7 @@ async function selectPreset(name) {
327 saveSettingsDebounced();324 saveSettingsDebounced();
328}325}
329326
330function formatTextGenURL(value) {327export function formatTextGenURL(value) {
331 try {328 try {
332 const noFormatTypes = [MANCER, TOGETHERAI, INFERMATICAI, DREAMGEN, OPENROUTER];329 const noFormatTypes = [MANCER, TOGETHERAI, INFERMATICAI, DREAMGEN, OPENROUTER];
333 if (noFormatTypes.includes(settings.type)) {330 if (noFormatTypes.includes(settings.type)) {
@@ -465,7 +462,7 @@ function calculateLogitBias() {
465 return result;462 return result;
466}463}
467464
468function loadTextGenSettings(data, loadedSettings) {465export function loadTextGenSettings(data, loadedSettings) {
469 textgenerationwebui_presets = convertPresets(data.textgenerationwebui_presets);466 textgenerationwebui_presets = convertPresets(data.textgenerationwebui_presets);
470 textgenerationwebui_preset_names = data.textgenerationwebui_preset_names ?? [];467 textgenerationwebui_preset_names = data.textgenerationwebui_preset_names ?? [];
471 Object.assign(settings, loadedSettings.textgenerationwebui_settings ?? {});468 Object.assign(settings, loadedSettings.textgenerationwebui_settings ?? {});
@@ -889,7 +886,7 @@ function setSettingByName(setting, value, trigger) {
889 * @returns {Promise<(function(): AsyncGenerator<{swipes: [], text: string, toolCalls: [], logprobs: {token: string, topLogprobs: Candidate[]}|null}, void, *>)|*>}886 * @returns {Promise<(function(): AsyncGenerator<{swipes: [], text: string, toolCalls: [], logprobs: {token: string, topLogprobs: Candidate[]}|null}, void, *>)|*>}
890 * @throws {Error} - If the response status is not OK, or from within the generator887 * @throws {Error} - If the response status is not OK, or from within the generator
891 */888 */
892async function generateTextGenWithStreaming(generate_data, signal) {889export async function generateTextGenWithStreaming(generate_data, signal) {
893 generate_data.stream = true;890 generate_data.stream = true;
894891
895 const response = await fetch('/api/backends/text-completions/generate', {892 const response = await fetch('/api/backends/text-completions/generate', {
public/scripts/tokenizers.js+20 -10
@@ -8,8 +8,6 @@ import { kai_flags } from './kai-settings.js';
8import { textgen_types, textgenerationwebui_settings as textgen_settings, getTextGenServer, getTextGenModel } from './textgen-settings.js';8import { textgen_types, textgenerationwebui_settings as textgen_settings, getTextGenServer, getTextGenModel } from './textgen-settings.js';
9import { getCurrentDreamGenModelTokenizer, getCurrentOpenRouterModelTokenizer, openRouterModels } from './textgen-models.js';9import { getCurrentDreamGenModelTokenizer, getCurrentOpenRouterModelTokenizer, openRouterModels } from './textgen-models.js';
1010
11const { OOBA, TABBY, KOBOLDCPP, VLLM, APHRODITE, LLAMACPP, OPENROUTER, DREAMGEN } = textgen_types;
12
13export const CHARACTERS_PER_TOKEN_RATIO = 3.35;11export const CHARACTERS_PER_TOKEN_RATIO = 3.35;
14export const TOKENIZER_WARNING_KEY = 'tokenizationWarningShown';12export const TOKENIZER_WARNING_KEY = 'tokenizationWarningShown';
15export const TOKENIZER_SUPPORTED_KEY = 'tokenizationSupported';13export const TOKENIZER_SUPPORTED_KEY = 'tokenizationSupported';
@@ -52,8 +50,12 @@ export const ENCODE_TOKENIZERS = [
52 //tokenizers.NERD2,50 //tokenizers.NERD2,
53];51];
5452
55// A list of Text Completion sources that support remote tokenization.53/**
56export 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 */
58export const TEXTGEN_TOKENIZERS = [];
5759
58const TOKENIZER_URLS = {60const TOKENIZER_URLS = {
59 [tokenizers.GPT2]: {61 [tokenizers.GPT2]: {
@@ -287,7 +289,7 @@ export function getTokenizerBestMatch(forApi) {
287 const hasTokenizerError = sessionStorage.getItem(TOKENIZER_WARNING_KEY);289 const hasTokenizerError = sessionStorage.getItem(TOKENIZER_WARNING_KEY);
288 const hasValidEndpoint = sessionStorage.getItem(TOKENIZER_SUPPORTED_KEY);290 const hasValidEndpoint = sessionStorage.getItem(TOKENIZER_SUPPORTED_KEY);
289 const isConnected = online_status !== 'no_connection';291 const isConnected = online_status !== 'no_connection';
290 const isTokenizerSupported = TEXTGEN_TOKENIZERS.includes(textgen_settings.type) && (textgen_settings.type !== OOBA || hasValidEndpoint);292 const isTokenizerSupported = TEXTGEN_TOKENIZERS.includes(textgen_settings.type) && (textgen_settings.type !== textgen_types.OOBA || hasValidEndpoint);
291293
292 if (!hasTokenizerError && isConnected) {294 if (!hasTokenizerError && isConnected) {
293 if (forApi === 'kobold' && kai_flags.can_use_tokenization) {295 if (forApi === 'kobold' && kai_flags.can_use_tokenization) {
@@ -297,10 +299,10 @@ export function getTokenizerBestMatch(forApi) {
297 if (forApi === 'textgenerationwebui' && isTokenizerSupported) {299 if (forApi === 'textgenerationwebui' && isTokenizerSupported) {
298 return tokenizers.API_TEXTGENERATIONWEBUI;300 return tokenizers.API_TEXTGENERATIONWEBUI;
299 }301 }
300 if (forApi === 'textgenerationwebui' && textgen_settings.type === OPENROUTER) {302 if (forApi === 'textgenerationwebui' && textgen_settings.type === textgen_types.OPENROUTER) {
301 return getCurrentOpenRouterModelTokenizer();303 return getCurrentOpenRouterModelTokenizer();
302 }304 }
303 if (forApi === 'textgenerationwebui' && textgen_settings.type === DREAMGEN) {305 if (forApi === 'textgenerationwebui' && textgen_settings.type === textgen_types.DREAMGEN) {
304 return getCurrentDreamGenModelTokenizer();306 return getCurrentDreamGenModelTokenizer();
305 }307 }
306 }308 }
@@ -576,7 +578,7 @@ export function getTokenizerModel() {
576578
577 // And for OpenRouter (if not a site model, then it's impossible to determine the tokenizer)579 // And for OpenRouter (if not a site model, then it's impossible to determine the tokenizer)
578 if (main_api == 'openai' && oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER && oai_settings.openrouter_model ||580 if (main_api == 'openai' && oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER && oai_settings.openrouter_model ||
579 main_api == 'textgenerationwebui' && textgen_settings.type === OPENROUTER && textgen_settings.openrouter_model) {581 main_api == 'textgenerationwebui' && textgen_settings.type === textgen_types.OPENROUTER && textgen_settings.openrouter_model) {
580 const model = main_api == 'openai'582 const model = main_api == 'openai'
581 ? model_list.find(x => x.id === oai_settings.openrouter_model)583 ? model_list.find(x => x.id === oai_settings.openrouter_model)
582 : openRouterModels.find(x => x.id === textgen_settings.openrouter_model);584 : openRouterModels.find(x => x.id === textgen_settings.openrouter_model);
@@ -652,7 +654,7 @@ export function getTokenizerModel() {
652 return oai_settings.custom_model;654 return oai_settings.custom_model;
653 }655 }
654656
655 if (oai_settings.chat_completion_source === chat_completion_sources.PERPLEXITY) {657 if (oai_settings.chat_completion_source === chat_completion_sources.PERPLEXITY) {
656 if (oai_settings.perplexity_model.includes('llama-3') || oai_settings.perplexity_model.includes('llama3')) {658 if (oai_settings.perplexity_model.includes('llama-3') || oai_settings.perplexity_model.includes('llama3')) {
657 return llama3Tokenizer;659 return llama3Tokenizer;
658 }660 }
@@ -680,7 +682,7 @@ export function getTokenizerModel() {
680 return yiTokenizer;682 return yiTokenizer;
681 }683 }
682684
683 if (oai_settings.chat_completion_source === chat_completion_sources.BLOCKENTROPY) {685 if (oai_settings.chat_completion_source === chat_completion_sources.BLOCKENTROPY) {
684 if (oai_settings.blockentropy_model.includes('llama3')) {686 if (oai_settings.blockentropy_model.includes('llama3')) {
685 return llama3Tokenizer;687 return llama3Tokenizer;
686 }688 }
@@ -1121,6 +1123,14 @@ export function decodeTextTokens(tokenizerType, ids) {
1121}1123}
11221124
1123export async function initTokenizers() {1125export 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 );
1124 await loadTokenCache();1134 await loadTokenCache();
1125 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);1135 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);
1126}1136}
public/scripts/tool-calling.js+1 -1
@@ -947,7 +947,7 @@ export class ToolManager {
947 enumProvider: toolsEnumProvider,947 enumProvider: toolsEnumProvider,
948 }),948 }),
949 ],949 ],
950 callback: async (name) => {950 callback: async (_, name) => {
951 if (typeof name !== 'string' || !name) {951 if (typeof name !== 'string' || !name) {
952 throw new Error('The unnamed argument must be a non-empty string.');952 throw new Error('The unnamed argument must be a non-empty string.');
953 }953 }
public/style.css+8 -2
@@ -1040,8 +1040,14 @@ body .panelControlBar {
1040 height: var(--swipeCounterHeight);1040 height: var(--swipeCounterHeight);
1041}1041}
10421042
1043.mes:not(.last_mes) .swipes-counter {1043body:not(.swipeAllMessages) .mes:not(.last_mes) .swipes-counter {
1044 opacity: 0.3;1044 visibility: hidden;
1045}
1046
1047body.swipeAllMessages .mes:not(.last_mes) .swipes-counter {
1048 /* Avoid expensive DOM queries */
1049 opacity: 0.3 !important;
1050 display: flex !important;
1045}1051}
10461052
1047.swipe_left {1053.swipe_left {
src/endpoints/backends/chat-completions.js+1 -1
@@ -280,7 +280,7 @@ async function sendMakerSuiteRequest(request, response) {
280 delete generationConfig.stopSequences;280 delete generationConfig.stopSequences;
281 }281 }
282282
283 const should_use_system_prompt = (model.includes('gemini-1.5-flash') || model.includes('gemini-1.5-pro') || model.includes('gemini-exp-1114') || model.includes('gemini-exp-1121')) && request.body.use_makersuite_sysprompt;283 const should_use_system_prompt = (model.includes('gemini-1.5-flash') || model.includes('gemini-1.5-pro') || model.startsWith('gemini-exp')) && request.body.use_makersuite_sysprompt;
284 const prompt = convertGooglePrompt(request.body.messages, model, should_use_system_prompt, request.body.char_name, request.body.user_name);284 const prompt = convertGooglePrompt(request.body.messages, model, should_use_system_prompt, request.body.char_name, request.body.user_name);
285 let body = {285 let body = {
286 contents: prompt.contents,286 contents: prompt.contents,
src/endpoints/characters.js+4 -3
@@ -14,7 +14,7 @@ import jimp from 'jimp';
1414
15import { AVATAR_WIDTH, AVATAR_HEIGHT } from '../constants.js';15import { AVATAR_WIDTH, AVATAR_HEIGHT } from '../constants.js';
16import { jsonParser, urlencodedParser } from '../express-common.js';16import { jsonParser, urlencodedParser } from '../express-common.js';
17import { deepMerge, humanizedISO8601DateTime, tryParse, extractFileFromZipBuffer, MemoryLimitedMap } from '../util.js';17import { deepMerge, humanizedISO8601DateTime, tryParse, extractFileFromZipBuffer, MemoryLimitedMap, getConfigValue } from '../util.js';
18import { TavernCardValidator } from '../validator/TavernCardValidator.js';18import { TavernCardValidator } from '../validator/TavernCardValidator.js';
19import { parse, write } from '../character-card-parser.js';19import { parse, write } from '../character-card-parser.js';
20import { readWorldInfoFile } from './worldinfo.js';20import { readWorldInfoFile } from './worldinfo.js';
@@ -23,8 +23,9 @@ import { importRisuSprites } from './sprites.js';
23const defaultAvatarPath = './public/img/ai4.png';23const defaultAvatarPath = './public/img/ai4.png';
2424
25// KV-store for parsed character data25// KV-store for parsed character data
26// 100 MB limit. Would take roughly 3000 characters to reach this limit26const cacheCapacity = Number(getConfigValue('cardsCacheCapacity', 100)); // MB
27const characterDataCache = new MemoryLimitedMap(1024 * 1024 * 100);27// With 100 MB limit it would take roughly 3000 characters to reach this limit
28const characterDataCache = new MemoryLimitedMap(1024 * 1024 * cacheCapacity);
28// Some Android devices require tighter memory management29// Some Android devices require tighter memory management
29const isAndroid = process.platform === 'android';30const isAndroid = process.platform === 'android';
3031
src/endpoints/chats.js+4 -4
@@ -203,19 +203,19 @@ function importKoboldLiteChat(_userName, _characterName, data) {
203203
204 /** @type {function(string): object} */204 /** @type {function(string): object} */
205 function processKoboldMessage(msg) {205 function processKoboldMessage(msg) {
206 const isUser = msg.includes(inputToken) || msg.includes(outputToken);206 const isUser = msg.includes(inputToken);
207 return {207 return {
208 name: isUser ? header.user_name : header.character_name,208 name: isUser ? header.user_name : header.character_name,
209 is_user: isUser,209 is_user: isUser,
210 mes: msg.replace(inputToken, '').replace(outputToken, '').trim(),210 mes: msg.replaceAll(inputToken, '').replaceAll(outputToken, '').trim(),
211 send_date: Date.now(),211 send_date: Date.now(),
212 };212 };
213 }213 }
214214
215 // Create the header215 // Create the header
216 const header = {216 const header = {
217 user_name: data.savedsettings.chatname,217 user_name: String(data.savedsettings.chatname),
218 character_name: data.savedsettings.chatopponent,218 character_name: String(data.savedsettings.chatopponent).split('||$||')[0],
219 };219 };
220 // Format messages220 // Format messages
221 const formattedMessages = data.actions.map(processKoboldMessage);221 const formattedMessages = data.actions.map(processKoboldMessage);
src/endpoints/users-public.js+8 -8
@@ -67,17 +67,17 @@ router.post('/login', jsonParser, async (request, response) => {
67 const user = await storage.getItem(toKey(request.body.handle));67 const user = await storage.getItem(toKey(request.body.handle));
6868
69 if (!user) {69 if (!user) {
70 console.log('Login failed: User not found');70 console.log('Login failed: User', request.body.handle, 'not found');
71 return response.status(403).json({ error: 'Incorrect credentials' });71 return response.status(403).json({ error: 'Incorrect credentials' });
72 }72 }
7373
74 if (!user.enabled) {74 if (!user.enabled) {
75 console.log('Login failed: User is disabled');75 console.log('Login failed: User', user.handle, 'is disabled');
76 return response.status(403).json({ error: 'User is disabled' });76 return response.status(403).json({ error: 'User is disabled' });
77 }77 }
7878
79 if (user.password && user.password !== getPasswordHash(request.body.password, user.salt)) {79 if (user.password && user.password !== getPasswordHash(request.body.password, user.salt)) {
80 console.log('Login failed: Incorrect password');80 console.log('Login failed: Incorrect password for', user.handle);
81 return response.status(403).json({ error: 'Incorrect credentials' });81 return response.status(403).json({ error: 'Incorrect credentials' });
82 }82 }
8383
@@ -88,7 +88,7 @@ router.post('/login', jsonParser, async (request, response) => {
8888
89 await loginLimiter.delete(ip);89 await loginLimiter.delete(ip);
90 request.session.handle = user.handle;90 request.session.handle = user.handle;
91 console.log('Login successful:', user.handle, request.session);91 console.log('Login successful:', user.handle, 'from', ip, 'at', new Date().toLocaleString());
92 return response.json({ handle: user.handle });92 return response.json({ handle: user.handle });
93 } catch (error) {93 } catch (error) {
94 if (error instanceof RateLimiterRes) {94 if (error instanceof RateLimiterRes) {
@@ -115,12 +115,12 @@ router.post('/recover-step1', jsonParser, async (request, response) => {
115 const user = await storage.getItem(toKey(request.body.handle));115 const user = await storage.getItem(toKey(request.body.handle));
116116
117 if (!user) {117 if (!user) {
118 console.log('Recover step 1 failed: User not found');118 console.log('Recover step 1 failed: User', request.body.handle, 'not found');
119 return response.status(404).json({ error: 'User not found' });119 return response.status(404).json({ error: 'User not found' });
120 }120 }
121121
122 if (!user.enabled) {122 if (!user.enabled) {
123 console.log('Recover step 1 failed: User is disabled');123 console.log('Recover step 1 failed: User', user.handle, 'is disabled');
124 return response.status(403).json({ error: 'User is disabled' });124 return response.status(403).json({ error: 'User is disabled' });
125 }125 }
126126
@@ -153,12 +153,12 @@ router.post('/recover-step2', jsonParser, async (request, response) => {
153 const ip = getIpFromRequest(request);153 const ip = getIpFromRequest(request);
154154
155 if (!user) {155 if (!user) {
156 console.log('Recover step 2 failed: User not found');156 console.log('Recover step 2 failed: User', request.body.handle, 'not found');
157 return response.status(404).json({ error: 'User not found' });157 return response.status(404).json({ error: 'User not found' });
158 }158 }
159159
160 if (!user.enabled) {160 if (!user.enabled) {
161 console.log('Recover step 2 failed: User is disabled');161 console.log('Recover step 2 failed: User', user.handle, 'is disabled');
162 return response.status(403).json({ error: 'User is disabled' });162 return response.status(403).json({ error: 'User is disabled' });
163 }163 }
164164
src/prompt-converters.js+1 -0
@@ -347,6 +347,7 @@ export function convertGooglePrompt(messages, model, useSysPrompt = false, charN
347 'gemini-1.5-flash-8b-exp-0924',347 'gemini-1.5-flash-8b-exp-0924',
348 'gemini-exp-1114',348 'gemini-exp-1114',
349 'gemini-exp-1121',349 'gemini-exp-1121',
350 'gemini-exp-1206',
350 'gemini-1.5-pro',351 'gemini-1.5-pro',
351 'gemini-1.5-pro-latest',352 'gemini-1.5-pro-latest',
352 'gemini-1.5-pro-001',353 'gemini-1.5-pro-001',
src/util.js+3 -2
@@ -680,8 +680,9 @@ export class MemoryLimitedMap {
680 * @param {number} maxMemoryInBytes - The maximum allowed memory in bytes for string values.680 * @param {number} maxMemoryInBytes - The maximum allowed memory in bytes for string values.
681 */681 */
682 constructor(maxMemoryInBytes) {682 constructor(maxMemoryInBytes) {
683 if (typeof maxMemoryInBytes !== 'number' || maxMemoryInBytes <= 0) {683 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
685 }686 }
686 this.maxMemory = maxMemoryInBytes;687 this.maxMemory = maxMemoryInBytes;
687 this.currentMemory = 0;688 this.currentMemory = 0;