Merge pull request #3156 from SillyTavern/staging Staging

bf4357774dc8ba1171899687181edea071b08221

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

Signed
33 files changed, +614 -318Ignore 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+2 -2
@@ -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": {
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+8 -7
@@ -669,7 +669,7 @@
669 </span>669 </span>
670 </div>670 </div>
671 </div>671 </div>
672 <div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai">672 <div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt">
673 <div class="range-block-title" data-i18n="Temperature">673 <div class="range-block-title" data-i18n="Temperature">
674 Temperature674 Temperature
675 </div>675 </div>
@@ -682,7 +682,7 @@
682 </div>682 </div>
683 </div>683 </div>
684 </div>684 </div>
685 <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai">685 <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt">
686 <div class="range-block-title" data-i18n="Frequency Penalty">686 <div class="range-block-title" data-i18n="Frequency Penalty">
687 Frequency Penalty687 Frequency Penalty
688 </div>688 </div>
@@ -695,7 +695,7 @@
695 </div>695 </div>
696 </div>696 </div>
697 </div>697 </div>
698 <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai">698 <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt">
699 <div class="range-block-title" data-i18n="Presence Penalty">699 <div class="range-block-title" data-i18n="Presence Penalty">
700 Presence Penalty700 Presence Penalty
701 </div>701 </div>
@@ -721,7 +721,7 @@
721 </div>721 </div>
722 </div>722 </div>
723 </div>723 </div>
724 <div class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai">724 <div class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt">
725 <div class="range-block-title" data-i18n="Top P">725 <div class="range-block-title" data-i18n="Top P">
726 Top P726 Top P
727 </div>727 </div>
@@ -958,7 +958,7 @@
958 </div>958 </div>
959 </div>959 </div>
960 </div>960 </div>
961 <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq">961 <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,nanogpt">
962 <div class="range-block-title justifyLeft" data-i18n="Seed">962 <div class="range-block-title justifyLeft" data-i18n="Seed">
963 Seed963 Seed
964 </div>964 </div>
@@ -1425,7 +1425,7 @@
1425 <input class="neo-range-slider" type="range" id="dry_allowed_length_textgenerationwebui" min="1" max="20" step="1" />1425 <input class="neo-range-slider" type="range" id="dry_allowed_length_textgenerationwebui" min="1" max="20" step="1" />
1426 <input class="neo-range-input" type="number" min="1" max="20" step="1" data-for="dry_allowed_length_textgenerationwebui" id="dry_allowed_length_counter_textgenerationwebui">1426 <input class="neo-range-input" type="number" min="1" max="20" step="1" data-for="dry_allowed_length_textgenerationwebui" id="dry_allowed_length_counter_textgenerationwebui">
1427 </div>1427 </div>
1428 <div class="alignItemsCenter flex-container flexFlowColumn flexBasis48p flexGrow flexShrink gap0" data-tg-type="llamacpp, koboldcpp, tabby">1428 <div class="alignItemsCenter flex-container flexFlowColumn flexBasis48p flexGrow flexShrink gap0" data-tg-type="llamacpp, koboldcpp, tabby, aphrodite">
1429 <small data-i18n="Penalty Range">Penalty Range</small>1429 <small data-i18n="Penalty Range">Penalty Range</small>
1430 <input class="neo-range-slider" type="range" id="dry_penalty_last_n_textgenerationwebui" min="0" max="8192" step="1" />1430 <input class="neo-range-slider" type="range" id="dry_penalty_last_n_textgenerationwebui" min="0" max="8192" step="1" />
1431 <input class="neo-range-input" type="number" min="0" max="8192" step="1" data-for="dry_penalty_last_n_textgenerationwebui" id="dry_penalty_last_n_counter_textgenerationwebui">1431 <input class="neo-range-input" type="number" min="0" max="8192" step="1" data-for="dry_penalty_last_n_textgenerationwebui" id="dry_penalty_last_n_counter_textgenerationwebui">
@@ -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+76 -163
@@ -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,
@@ -241,12 +236,12 @@ import { hideLoader, showLoader } from './scripts/loader.js';
241import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js';236import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js';
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 { initPresetManager } from './scripts/preset-manager.js';239import { getPresetManager, initPresetManager } from './scripts/preset-manager.js';
245import { MacrosParser, evaluateMacros, getLastMessageId } 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
@@ -957,6 +955,7 @@ async function firstLoadInit() {
957 initDynamicStyles();955 initDynamicStyles();
958 initTags();956 initTags();
959 initBookmarks();957 initBookmarks();
958 initMacros();
960 await getUserAvatars(true, user_avatar);959 await getUserAvatars(true, user_avatar);
961 await getCharacters();960 await getCharacters();
962 await getBackgrounds();961 await getBackgrounds();
@@ -1174,7 +1173,7 @@ async function getStatusTextgen() {
1174 return resultCheckStatus();1173 return resultCheckStatus();
1175 }1174 }
11761175
1177 if (textgen_settings.type == OOBA && textgen_settings.bypass_status_check) {1176 if (textgen_settings.type == textgen_types.OOBA && textgen_settings.bypass_status_check) {
1178 setOnlineStatus('Status check bypassed');1177 setOnlineStatus('Status check bypassed');
1179 return resultCheckStatus();1178 return resultCheckStatus();
1180 }1179 }
@@ -1192,34 +1191,34 @@ async function getStatusTextgen() {
11921191
1193 const data = await response.json();1192 const data = await response.json();
11941193
1195 if (textgen_settings.type === MANCER) {1194 if (textgen_settings.type === textgen_types.MANCER) {
1196 loadMancerModels(data?.data);1195 loadMancerModels(data?.data);
1197 setOnlineStatus(textgen_settings.mancer_model);1196 setOnlineStatus(textgen_settings.mancer_model);
1198 } else if (textgen_settings.type === TOGETHERAI) {1197 } else if (textgen_settings.type === textgen_types.TOGETHERAI) {
1199 loadTogetherAIModels(data?.data);1198 loadTogetherAIModels(data?.data);
1200 setOnlineStatus(textgen_settings.togetherai_model);1199 setOnlineStatus(textgen_settings.togetherai_model);
1201 } else if (textgen_settings.type === OLLAMA) {1200 } else if (textgen_settings.type === textgen_types.OLLAMA) {
1202 loadOllamaModels(data?.data);1201 loadOllamaModels(data?.data);
1203 setOnlineStatus(textgen_settings.ollama_model || 'Connected');1202 setOnlineStatus(textgen_settings.ollama_model || 'Connected');
1204 } else if (textgen_settings.type === INFERMATICAI) {1203 } else if (textgen_settings.type === textgen_types.INFERMATICAI) {
1205 loadInfermaticAIModels(data?.data);1204 loadInfermaticAIModels(data?.data);
1206 setOnlineStatus(textgen_settings.infermaticai_model);1205 setOnlineStatus(textgen_settings.infermaticai_model);
1207 } else if (textgen_settings.type === DREAMGEN) {1206 } else if (textgen_settings.type === textgen_types.DREAMGEN) {
1208 loadDreamGenModels(data?.data);1207 loadDreamGenModels(data?.data);
1209 setOnlineStatus(textgen_settings.dreamgen_model);1208 setOnlineStatus(textgen_settings.dreamgen_model);
1210 } else if (textgen_settings.type === OPENROUTER) {1209 } else if (textgen_settings.type === textgen_types.OPENROUTER) {
1211 loadOpenRouterModels(data?.data);1210 loadOpenRouterModels(data?.data);
1212 setOnlineStatus(textgen_settings.openrouter_model);1211 setOnlineStatus(textgen_settings.openrouter_model);
1213 } else if (textgen_settings.type === VLLM) {1212 } else if (textgen_settings.type === textgen_types.VLLM) {
1214 loadVllmModels(data?.data);1213 loadVllmModels(data?.data);
1215 setOnlineStatus(textgen_settings.vllm_model);1214 setOnlineStatus(textgen_settings.vllm_model);
1216 } else if (textgen_settings.type === APHRODITE) {1215 } else if (textgen_settings.type === textgen_types.APHRODITE) {
1217 loadAphroditeModels(data?.data);1216 loadAphroditeModels(data?.data);
1218 setOnlineStatus(textgen_settings.aphrodite_model);1217 setOnlineStatus(textgen_settings.aphrodite_model);
1219 } else if (textgen_settings.type === FEATHERLESS) {1218 } else if (textgen_settings.type === textgen_types.FEATHERLESS) {
1220 loadFeatherlessModels(data?.data);1219 loadFeatherlessModels(data?.data);
1221 setOnlineStatus(textgen_settings.featherless_model);1220 setOnlineStatus(textgen_settings.featherless_model);
1222 } else if (textgen_settings.type === TABBY) {1221 } else if (textgen_settings.type === textgen_types.TABBY) {
1223 loadTabbyModels(data?.data);1222 loadTabbyModels(data?.data);
1224 setOnlineStatus(textgen_settings.tabby_model || data?.result);1223 setOnlineStatus(textgen_settings.tabby_model || data?.result);
1225 } else {1224 } else {
@@ -1636,7 +1635,7 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) {
1636 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 });
1637 }1636 }
1638 if (doSort) {1637 if (doSort) {
1639 sortEntitiesList(subEntities);1638 sortEntitiesList(subEntities, false);
1640 }1639 }
1641 entity.entities = subEntities;1640 entity.entities = subEntities;
1642 entity.hidden = subCount - subEntities.length;1641 entity.hidden = subCount - subEntities.length;
@@ -1664,7 +1663,7 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) {
16641663
1665 // Sort before returning if requested1664 // Sort before returning if requested
1666 if (doSort) {1665 if (doSort) {
1667 sortEntitiesList(entities);1666 sortEntitiesList(entities, false);
1668 }1667 }
1669 entitiesFilter.clearFuzzySearchCaches();1668 entitiesFilter.clearFuzzySearchCaches();
1670 return entities;1669 return entities;
@@ -2606,15 +2605,18 @@ export function substituteParams(content, _name1, _name2, _original, _group, _re
2606 };2605 };
2607 }2606 }
26082607
2609 const getGroupValue = () => {2608 const getGroupValue = (includeMuted) => {
2610 if (typeof _group === 'string') {2609 if (typeof _group === 'string') {
2611 return _group;2610 return _group;
2612 }2611 }
26132612
2614 if (selected_group) {2613 if (selected_group) {
2615 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);
2616 const names = Array.isArray(members)2618 const names = Array.isArray(members)
2617 ? 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(', ')
2618 : '';2620 : '';
2619 return names;2621 return names;
2620 } else {2622 } else {
@@ -2638,7 +2640,8 @@ export function substituteParams(content, _name1, _name2, _original, _group, _re
2638 // Must be substituted last so that they're replaced inside {{description}}2640 // Must be substituted last so that they're replaced inside {{description}}
2639 environment.user = _name1 ?? name1;2641 environment.user = _name1 ?? name1;
2640 environment.char = _name2 ?? name2;2642 environment.char = _name2 ?? name2;
2641 environment.group = environment.charIfNotGroup = getGroupValue();2643 environment.group = environment.charIfNotGroup = getGroupValue(true);
2644 environment.groupNotMuted = getGroupValue(false);
2642 environment.model = getGeneratingModel();2645 environment.model = getGeneratingModel();
26432646
2644 if (additionalMacro && typeof additionalMacro === 'object') {2647 if (additionalMacro && typeof additionalMacro === 'object') {
@@ -4493,6 +4496,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
4493 instruction: main_api !== 'openai' && power_user.sysprompt.enabled ? substituteParams(power_user.prefer_character_prompt && system ? system : power_user.sysprompt.content) : '',4496 instruction: main_api !== 'openai' && power_user.sysprompt.enabled ? substituteParams(power_user.prefer_character_prompt && system ? system : power_user.sysprompt.content) : '',
4494 userPersona: (power_user.persona_description_position == persona_description_positions.IN_PROMPT ? (persona || '') : ''),4497 userPersona: (power_user.persona_description_position == persona_description_positions.IN_PROMPT ? (persona || '') : ''),
4495 tokenizer: getFriendlyTokenizerName(main_api).tokenizerName || '',4498 tokenizer: getFriendlyTokenizerName(main_api).tokenizerName || '',
4499 presetName: getPresetManager()?.getSelectedPresetName() || '',
4496 };4500 };
44974501
4498 //console.log(additionalPromptStuff);4502 //console.log(additionalPromptStuff);
@@ -5159,6 +5163,7 @@ export async function itemizedParams(itemizedPrompts, thisPromptSet, incomingMes
5159 dataBankVectorsStringTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].dataBankVectorsString),5163 dataBankVectorsStringTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].dataBankVectorsString),
5160 modelUsed: chat[incomingMesId]?.extra?.model,5164 modelUsed: chat[incomingMesId]?.extra?.model,
5161 apiUsed: chat[incomingMesId]?.extra?.api,5165 apiUsed: chat[incomingMesId]?.extra?.api,
5166 presetName: itemizedPrompts[thisPromptSet].presetName || t`(Unknown)`,
5162 };5167 };
51635168
5164 const getFriendlyName = (value) => $(`#rm_api_block select option[value="${value}"]`).first().text() || value;5169 const getFriendlyName = (value) => $(`#rm_api_block select option[value="${value}"]`).first().text() || value;
@@ -5535,7 +5540,7 @@ function extractMultiSwipes(data, type) {
5535 return swipes;5540 return swipes;
5536 }5541 }
55375542
5538 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))) {
5539 if (!Array.isArray(data.choices)) {5544 if (!Array.isArray(data.choices)) {
5540 return swipes;5545 return swipes;
5541 }5546 }
@@ -5698,7 +5703,7 @@ export function cleanUpMessage(getMessage, isImpersonate, isContinue, displayInc
5698 return getMessage;5703 return getMessage;
5699}5704}
57005705
5701async function saveReply(type, getMessage, fromStreaming, title, swipes) {5706export async function saveReply(type, getMessage, fromStreaming, title, swipes) {
5702 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 ||
5703 chat[chat.length - 1]['is_user'])) {5708 chat[chat.length - 1]['is_user'])) {
5704 type = 'normal';5709 type = 'normal';
@@ -7657,14 +7662,12 @@ export function showSwipeButtons() {
7657 //allows for writing individual swipe counters for past messages7662 //allows for writing individual swipe counters for past messages
7658 const lastSwipeCounter = $('.last_mes .swipes-counter');7663 const lastSwipeCounter = $('.last_mes .swipes-counter');
7659 lastSwipeCounter.text(swipeCounterText).show();7664 lastSwipeCounter.text(swipeCounterText).show();
7660
7661 switchSwipeNumAllMessages();
7662}7665}
76637666
7664export function hideSwipeButtons() {7667export function hideSwipeButtons() {
7665 $('#chat').find('.swipe_right').hide();7668 chatElement.find('.swipe_right').hide();
7666 $('#chat').find('.last_mes .swipes-counter').hide();7669 chatElement.find('.last_mes .swipes-counter').hide();
7667 $('#chat').find('.swipe_left').hide();7670 chatElement.find('.swipe_left').hide();
7668}7671}
76697672
7670/**7673/**
@@ -7751,25 +7754,31 @@ export async function saveChatConditional() {
7751 }7754 }
7752}7755}
77537756
7754async function importCharacterChat(formData) {7757/**
7755 await jQuery.ajax({7758 * Saves the chat to the server.
7756 type: 'POST',7759 * @param {FormData} formData Form data to send to the server.
7757 url: '/api/chats/import',7760 * @param {EventTarget} eventTarget Event target to trigger the event on.
7758 data: formData,7761 */
7759 beforeSend: function () {7762async function importCharacterChat(formData, eventTarget) {
7760 },7763 const headers = getRequestHeaders();
7761 cache: false,7764 delete headers['Content-Type'];
7762 contentType: false,7765 const fetchResult = await fetch('/api/chats/import', {
7763 processData: false,7766 method: 'POST',
7764 success: async function (data) {7767 body: formData,
7765 if (data.res) {7768 headers: headers,
7766 await displayPastChats();7769 cache: 'no-cache',
7767 }
7768 },
7769 error: function () {
7770 $('#create_button').removeAttr('disabled');
7771 },
7772 });7770 });
7771
7772 if (fetchResult.ok) {
7773 const data = await fetchResult.json();
7774 if (data.res) {
7775 await displayPastChats();
7776 }
7777 }
7778
7779 if (eventTarget instanceof HTMLInputElement) {
7780 eventTarget.value = '';
7781 }
7773}7782}
77747783
7775function updateViewMessageIds(startFromZero = false) {7784function updateViewMessageIds(startFromZero = false) {
@@ -8224,102 +8233,6 @@ async function createOrEditCharacter(e) {
8224 }8233 }
8225}8234}
82268235
8227window['SillyTavern'].getContext = function () {
8228 return {
8229 chat: chat,
8230 characters: characters,
8231 groups: groups,
8232 name1: name1,
8233 name2: name2,
8234 characterId: this_chid,
8235 groupId: selected_group,
8236 chatId: selected_group
8237 ? groups.find(x => x.id == selected_group)?.chat_id
8238 : (this_chid && characters[this_chid] && characters[this_chid].chat),
8239 getCurrentChatId: getCurrentChatId,
8240 getRequestHeaders: getRequestHeaders,
8241 reloadCurrentChat: reloadCurrentChat,
8242 renameChat: renameChat,
8243 saveSettingsDebounced: saveSettingsDebounced,
8244 onlineStatus: online_status,
8245 maxContext: Number(max_context),
8246 chatMetadata: chat_metadata,
8247 streamingProcessor,
8248 eventSource: eventSource,
8249 eventTypes: event_types,
8250 addOneMessage: addOneMessage,
8251 generate: Generate,
8252 sendStreamingRequest: sendStreamingRequest,
8253 sendGenerationRequest: sendGenerationRequest,
8254 stopGeneration: stopGeneration,
8255 getTokenCount: getTokenCount,
8256 extensionPrompts: extension_prompts,
8257 setExtensionPrompt: setExtensionPrompt,
8258 updateChatMetadata: updateChatMetadata,
8259 saveChat: saveChatConditional,
8260 openCharacterChat: openCharacterChat,
8261 openGroupChat: openGroupChat,
8262 saveMetadata: saveMetadata,
8263 sendSystemMessage: sendSystemMessage,
8264 activateSendButtons,
8265 deactivateSendButtons,
8266 saveReply,
8267 substituteParams,
8268 substituteParamsExtended,
8269 SlashCommandParser,
8270 SlashCommand,
8271 SlashCommandArgument,
8272 SlashCommandNamedArgument,
8273 ARGUMENT_TYPE,
8274 executeSlashCommandsWithOptions,
8275 /** @deprecated Use SlashCommandParser.addCommandObject() instead */
8276 registerSlashCommand: registerSlashCommand,
8277 /** @deprecated Use executeSlashCommandWithOptions instead */
8278 executeSlashCommands: executeSlashCommands,
8279 timestampToMoment: timestampToMoment,
8280 /** @deprecated Handlebars for extensions are no longer supported. */
8281 registerHelper: () => { },
8282 registerMacro: MacrosParser.registerMacro.bind(MacrosParser),
8283 unregisterMacro: MacrosParser.unregisterMacro.bind(MacrosParser),
8284 registerFunctionTool: ToolManager.registerFunctionTool.bind(ToolManager),
8285 unregisterFunctionTool: ToolManager.unregisterFunctionTool.bind(ToolManager),
8286 isToolCallingSupported: ToolManager.isToolCallingSupported.bind(ToolManager),
8287 canPerformToolCalls: ToolManager.canPerformToolCalls.bind(ToolManager),
8288 registerDebugFunction: registerDebugFunction,
8289 /** @deprecated Use renderExtensionTemplateAsync instead. */
8290 renderExtensionTemplate: renderExtensionTemplate,
8291 renderExtensionTemplateAsync: renderExtensionTemplateAsync,
8292 registerDataBankScraper: ScraperManager.registerDataBankScraper,
8293 /** @deprecated Use callGenericPopup or Popup instead. */
8294 callPopup: callPopup,
8295 callGenericPopup: callGenericPopup,
8296 showLoader: showLoader,
8297 hideLoader: hideLoader,
8298 mainApi: main_api,
8299 extensionSettings: extension_settings,
8300 ModuleWorkerWrapper: ModuleWorkerWrapper,
8301 getTokenizerModel: getTokenizerModel,
8302 generateQuietPrompt: generateQuietPrompt,
8303 writeExtensionField: writeExtensionField,
8304 getThumbnailUrl: getThumbnailUrl,
8305 selectCharacterById: selectCharacterById,
8306 messageFormatting: messageFormatting,
8307 shouldSendOnEnter: shouldSendOnEnter,
8308 isMobile: isMobile,
8309 t: t,
8310 translate: translate,
8311 tags: tags,
8312 tagMap: tag_map,
8313 menuType: menu_type,
8314 createCharacterData: create_save,
8315 /** @deprecated Legacy snake-case naming, compatibility with old extensions */
8316 event_types: event_types,
8317 Popup: Popup,
8318 POPUP_TYPE: POPUP_TYPE,
8319 POPUP_RESULT: POPUP_RESULT,
8320 };
8321};
8322
8323/**8236/**
8324 * Formats a counter for a swipe view.8237 * Formats a counter for a swipe view.
8325 * @param {number} current The current number of items.8238 * @param {number} current The current number of items.
@@ -10826,13 +10739,13 @@ jQuery(async function () {
10826 });10739 });
1082710740
10828 $('#chat_import_file').on('change', async function (e) {10741 $('#chat_import_file').on('change', async function (e) {
10829 var file = e.target.files[0];10742 const file = e.target.files[0];
1083010743
10831 if (!file) {10744 if (!file) {
10832 return;10745 return;
10833 }10746 }
1083410747
10835 var ext = file.name.match(/\.(\w+)$/);10748 const ext = file.name.match(/\.(\w+)$/);
10836 if (10749 if (
10837 !ext ||10750 !ext ||
10838 (ext[1].toLowerCase() != 'json' && ext[1].toLowerCase() != 'jsonl')10751 (ext[1].toLowerCase() != 'json' && ext[1].toLowerCase() != 'jsonl')
@@ -10845,17 +10758,17 @@ jQuery(async function () {
10845 return;10758 return;
10846 }10759 }
1084710760
10848 var format = ext[1].toLowerCase();10761 const format = ext[1].toLowerCase();
10849 $('#chat_import_file_type').val(format);10762 $('#chat_import_file_type').val(format);
1085010763
10851 var formData = new FormData($('#form_import_chat').get(0));10764 const formData = new FormData($('#form_import_chat').get(0));
10852 formData.append('user_name', name1);10765 formData.append('user_name', name1);
10853 $('#select_chat_div').html('');10766 $('#select_chat_div').html('');
1085410767
10855 if (selected_group) {10768 if (selected_group) {
10856 await importGroupChat(formData);10769 await importGroupChat(formData, e.originalEvent.target);
10857 } else {10770 } else {
10858 await importCharacterChat(formData);10771 await importCharacterChat(formData, e.originalEvent.target);
10859 }10772 }
10860 });10773 });
1086110774
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+39 -31
@@ -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
@@ -1863,32 +1865,38 @@ export async function deleteGroupChat(groupId, chatId) {
1863 }1865 }
1864}1866}
18651867
1866export async function importGroupChat(formData) {1868/**
1867 await jQuery.ajax({1869 * Imports a group chat from a file and adds it to the group.
1868 type: 'POST',1870 * @param {FormData} formData Form data to send to the server
1869 url: '/api/chats/group/import',1871 * @param {EventTarget} eventTarget Element that triggered the import
1870 data: formData,1872 */
1871 beforeSend: function () {1873export async function importGroupChat(formData, eventTarget) {
1872 },1874 const headers = getRequestHeaders();
1873 cache: false,1875 delete headers['Content-Type'];
1874 contentType: false,1876 const fetchResult = await fetch('/api/chats/group/import', {
1875 processData: false,1877 method: 'POST',
1876 success: async function (data) {1878 headers: headers,
1877 if (data.res) {1879 body: formData,
1878 const chatId = data.res;1880 cache: 'no-cache',
1879 const group = groups.find(x => x.id == selected_group);
1880
1881 if (group) {
1882 group.chats.push(chatId);
1883 await editGroup(selected_group, true, true);
1884 await displayPastChats();
1885 }
1886 }
1887 },
1888 error: function () {
1889 $('#create_button').removeAttr('disabled');
1890 },
1891 });1881 });
1882
1883 if (fetchResult.ok) {
1884 const data = await fetchResult.json();
1885 if (data.res) {
1886 const chatId = data.res;
1887 const group = groups.find(x => x.id == selected_group);
1888
1889 if (group) {
1890 group.chats.push(chatId);
1891 await editGroup(selected_group, true, true);
1892 await displayPastChats();
1893 }
1894 }
1895 }
1896
1897 if (eventTarget instanceof HTMLInputElement) {
1898 eventTarget.value = '';
1899 }
1892}1900}
18931901
1894export async function saveGroupBookmarkChat(groupId, name, metadata, mesId) {1902export async function saveGroupBookmarkChat(groupId, name, metadata, mesId) {
public/scripts/macros.js+20 -1
@@ -1,5 +1,5 @@
1import { Handlebars, moment, seedrandom, droll } from '../lib.js';1import { Handlebars, moment, seedrandom, droll } from '../lib.js';
2import { chat, chat_metadata, main_api, getMaxContextSize, getCurrentChatId, substituteParams } from '../script.js';2import { chat, chat_metadata, main_api, getMaxContextSize, getCurrentChatId, substituteParams, eventSource, event_types } from '../script.js';
3import { timestampToMoment, isDigitsOnly, getStringHash, escapeRegex, uuidv4 } from './utils.js';3import { timestampToMoment, isDigitsOnly, getStringHash, escapeRegex, uuidv4 } from './utils.js';
4import { textgenerationwebui_banned_in_macros } from './textgen-settings.js';4import { textgenerationwebui_banned_in_macros } from './textgen-settings.js';
5import { getInstructMacros } from './instruct-mode.js';5import { getInstructMacros } from './instruct-mode.js';
@@ -521,3 +521,22 @@ export function evaluateMacros(content, env, postProcessFn) {
521521
522 return content;522 return content;
523}523}
524
525export function initMacros() {
526 function initLastGenerationType() {
527 let lastGenerationType = '';
528
529 MacrosParser.registerMacro('lastGenerationType', () => lastGenerationType);
530
531 eventSource.on(event_types.GENERATION_STARTED, (type, _params, isDryRun) => {
532 if (isDryRun) return;
533 lastGenerationType = type || 'normal';
534 });
535
536 eventSource.on(event_types.CHAT_CHANGED, () => {
537 lastGenerationType = '';
538 });
539 }
540
541 initLastGenerationType();
542}
public/scripts/openai.js+6 -4
@@ -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) {
@@ -1812,6 +1812,7 @@ async function sendOpenAIRequest(type, messages, signal) {
1812 const isPerplexity = oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY;1812 const isPerplexity = oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY;
1813 const isGroq = oai_settings.chat_completion_source == chat_completion_sources.GROQ;1813 const isGroq = oai_settings.chat_completion_source == chat_completion_sources.GROQ;
1814 const is01AI = oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI;1814 const is01AI = oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI;
1815 const isNano = oai_settings.chat_completion_source == chat_completion_sources.NANOGPT;
1815 const isTextCompletion = isOAI && textCompletionModels.includes(oai_settings.openai_model);1816 const isTextCompletion = isOAI && textCompletionModels.includes(oai_settings.openai_model);
1816 const isQuiet = type === 'quiet';1817 const isQuiet = type === 'quiet';
1817 const isImpersonate = type === 'impersonate';1818 const isImpersonate = type === 'impersonate';
@@ -1971,7 +1972,7 @@ async function sendOpenAIRequest(type, messages, signal) {
1971 delete generate_data.stop;1972 delete generate_data.stop;
1972 }1973 }
19731974
1974 if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere) && oai_settings.seed >= 0) {1975 if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere || isNano) && oai_settings.seed >= 0) {
1975 generate_data['seed'] = oai_settings.seed;1976 generate_data['seed'] = oai_settings.seed;
1976 }1977 }
19771978
@@ -4076,7 +4077,7 @@ async function onModelChange() {
4076 $('#openai_max_context').attr('max', max_2mil);4077 $('#openai_max_context').attr('max', max_2mil);
4077 } 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')) {
4078 $('#openai_max_context').attr('max', max_32k);4079 $('#openai_max_context').attr('max', max_32k);
4079 } else if (value.includes('gemini-1.5-pro')) {4080 } else if (value.includes('gemini-1.5-pro') || value.includes('gemini-exp-1206')) {
4080 $('#openai_max_context').attr('max', max_2mil);4081 $('#openai_max_context').attr('max', max_2mil);
4081 } else if (value.includes('gemini-1.5-flash')) {4082 } else if (value.includes('gemini-1.5-flash')) {
4082 $('#openai_max_context').attr('max', max_1mil);4083 $('#openai_max_context').attr('max', max_1mil);
@@ -4760,6 +4761,7 @@ export function isImageInliningSupported() {
4760 'gemini-1.5-flash-8b-exp-0924',4761 'gemini-1.5-flash-8b-exp-0924',
4761 'gemini-exp-1114',4762 'gemini-exp-1114',
4762 'gemini-exp-1121',4763 'gemini-exp-1121',
4764 'gemini-exp-1206',
4763 'gemini-1.0-pro-vision-latest',4765 'gemini-1.0-pro-vision-latest',
4764 'gemini-1.5-pro',4766 'gemini-1.5-pro',
4765 'gemini-1.5-pro-latest',4767 'gemini-1.5-pro-latest',
public/scripts/power-user.js+13 -12
@@ -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,
@@ -473,9 +472,9 @@ function switchCompactInputArea() {
473 $('#compact_input_area').prop('checked', power_user.compact_input_area);472 $('#compact_input_area').prop('checked', power_user.compact_input_area);
474}473}
475474
476export function switchSwipeNumAllMessages() {475function switchSwipeNumAllMessages() {
477 $('#show_swipe_num_all_messages').prop('checked', power_user.show_swipe_num_all_messages);476 $('#show_swipe_num_all_messages').prop('checked', power_user.show_swipe_num_all_messages);
478 $('.mes:not(.last_mes) .swipes-counter').css('opacity', '').toggle(power_user.show_swipe_num_all_messages);477 $('body').toggleClass('swipeAllMessages', !!power_user.show_swipe_num_all_messages);
479}478}
480479
481var originalSliderValues = [];480var originalSliderValues = [];
@@ -1837,7 +1836,7 @@ async function loadContextSettings() {
1837 * Common function to perform fuzzy search with optional caching1836 * Common function to perform fuzzy search with optional caching
1838 * @param {string} type - Type of search from fuzzySearchCategories1837 * @param {string} type - Type of search from fuzzySearchCategories
1839 * @param {any[]} data - Data array to search in1838 * @param {any[]} data - Data array to search in
1840 * @param {Array<{name: string, weight: number, getFn?: Function}>} keys - Fuse.js keys configuration1839 * @param {Array<{name: string, weight: number, getFn?: (obj: any) => string}>} keys - Fuse.js keys configuration
1841 * @param {string} searchValue - The search term1840 * @param {string} searchValue - The search term
1842 * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches1841 * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
1843 * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score1842 * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score
@@ -1851,7 +1850,6 @@ function performFuzzySearch(type, data, keys, searchValue, fuzzySearchCaches = n
1851 }1850 }
1852 }1851 }
18531852
1854 // @ts-ignore
1855 const fuse = new Fuse(data, {1853 const fuse = new Fuse(data, {
1856 keys: keys,1854 keys: keys,
1857 includeScore: true,1855 includeScore: true,
@@ -1925,7 +1923,7 @@ export function fuzzySearchPersonas(data, searchValue, fuzzySearchCaches = null)
1925 const mappedData = data.map(x => ({1923 const mappedData = data.map(x => ({
1926 key: x,1924 key: x,
1927 name: power_user.personas[x] ?? '',1925 name: power_user.personas[x] ?? '',
1928 description: power_user.persona_descriptions[x]?.description ?? ''1926 description: power_user.persona_descriptions[x]?.description ?? '',
1929 }));1927 }));
19301928
1931 const keys = [1929 const keys = [
@@ -2080,19 +2078,22 @@ const compareFunc = (first, second) => {
2080/**2078/**
2081 * Sorts an array of entities based on the current sort settings2079 * Sorts an array of entities based on the current sort settings
2082 * @param {any[]} entities An array of objects with an `item` property2080 * @param {any[]} entities An array of objects with an `item` property
2081 * @param {boolean} forceSearch Whether to force search sorting
2082 * @param {import('./filters.js').FilterHelper} [filterHelper=null] Filter helper to use
2083 */2083 */
2084function sortEntitiesList(entities) {2084export function sortEntitiesList(entities, forceSearch, filterHelper = null) {
2085 filterHelper = filterHelper ?? entitiesFilter;
2085 if (power_user.sort_field == undefined || entities.length === 0) {2086 if (power_user.sort_field == undefined || entities.length === 0) {
2086 return;2087 return;
2087 }2088 }
20882089
2089 if (power_user.sort_order === 'random') {2090 const isSearch = forceSearch || $('#character_sort_order option[data-field="search"]').is(':selected');
2091
2092 if (!isSearch && power_user.sort_order === 'random') {
2090 shuffle(entities);2093 shuffle(entities);
2091 return;2094 return;
2092 }2095 }
20932096
2094 const isSearch = $('#character_sort_order option[data-field="search"]').is(':selected');
2095
2096 entities.sort((a, b) => {2097 entities.sort((a, b) => {
2097 // Sort tags/folders will always be at the top2098 // Sort tags/folders will always be at the top
2098 if (a.type === 'tag' && b.type !== 'tag') {2099 if (a.type === 'tag' && b.type !== 'tag') {
@@ -2104,8 +2105,8 @@ function sortEntitiesList(entities) {
21042105
2105 // If we have search sorting, we take scores and use those2106 // If we have search sorting, we take scores and use those
2106 if (isSearch) {2107 if (isSearch) {
2107 const aScore = entitiesFilter.getScore(FILTER_TYPES.SEARCH, `${a.type}.${a.id}`);2108 const aScore = filterHelper.getScore(FILTER_TYPES.SEARCH, `${a.type}.${a.id}`);
2108 const bScore = entitiesFilter.getScore(FILTER_TYPES.SEARCH, `${b.type}.${b.id}`);2109 const bScore = filterHelper.getScore(FILTER_TYPES.SEARCH, `${b.type}.${b.id}`);
2109 return (aScore - bScore);2110 return (aScore - bScore);
2110 }2111 }
21112112
public/scripts/slash-commands.js+121 -12
@@ -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';
@@ -269,7 +269,7 @@ export function initDefaultSlashCommands() {
269 }),269 }),
270 SlashCommandNamedArgument.fromProps({270 SlashCommandNamedArgument.fromProps({
271 name: 'at',271 name: 'at',
272 description: 'position to insert the message (index-based, corresponding to message id). If not set, the message will be inserted at the end of the chat.\nNegative values are accepted and will work similarly to how \'depth\' usually works. For example, -1 will insert the message right before the last message in chat.',272 description: 'position to insert the message (index-based, corresponding to message id). If not set, the message will be inserted at the end of the chat.\nNegative values (including -0) are accepted and will work similarly to how \'depth\' usually works. For example, -1 will insert the message right before the last message in chat.',
273 typeList: [ARGUMENT_TYPE.NUMBER],273 typeList: [ARGUMENT_TYPE.NUMBER],
274 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),274 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
275 }),275 }),
@@ -325,7 +325,7 @@ export function initDefaultSlashCommands() {
325 ),325 ),
326 SlashCommandNamedArgument.fromProps({326 SlashCommandNamedArgument.fromProps({
327 name: 'at',327 name: 'at',
328 description: 'position to insert the message (index-based, corresponding to message id). If not set, the message will be inserted at the end of the chat.\nNegative values are accepted and will work similarly to how \'depth\' usually works. For example, -1 will insert the message right before the last message in chat.',328 description: 'position to insert the message (index-based, corresponding to message id). If not set, the message will be inserted at the end of the chat.\nNegative values (including -0) are accepted and will work similarly to how \'depth\' usually works. For example, -1 will insert the message right before the last message in chat.',
329 typeList: [ARGUMENT_TYPE.NUMBER],329 typeList: [ARGUMENT_TYPE.NUMBER],
330 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),330 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
331 }),331 }),
@@ -388,7 +388,7 @@ export function initDefaultSlashCommands() {
388 ),388 ),
389 SlashCommandNamedArgument.fromProps({389 SlashCommandNamedArgument.fromProps({
390 name: 'at',390 name: 'at',
391 description: 'position to insert the message (index-based, corresponding to message id). If not set, the message will be inserted at the end of the chat.\nNegative values are accepted and will work similarly to how \'depth\' usually works. For example, -1 will insert the message right before the last message in chat.',391 description: 'position to insert the message (index-based, corresponding to message id). If not set, the message will be inserted at the end of the chat.\nNegative values (including -0) are accepted and will work similarly to how \'depth\' usually works. For example, -1 will insert the message right before the last message in chat.',
392 typeList: [ARGUMENT_TYPE.NUMBER],392 typeList: [ARGUMENT_TYPE.NUMBER],
393 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),393 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
394 }),394 }),
@@ -606,7 +606,7 @@ export function initDefaultSlashCommands() {
606 ),606 ),
607 SlashCommandNamedArgument.fromProps({607 SlashCommandNamedArgument.fromProps({
608 name: 'at',608 name: 'at',
609 description: 'position to insert the message (index-based, corresponding to message id). If not set, the message will be inserted at the end of the chat.\nNegative values are accepted and will work similarly to how \'depth\' usually works. For example, -1 will insert the message right before the last message in chat.',609 description: 'position to insert the message (index-based, corresponding to message id). If not set, the message will be inserted at the end of the chat.\nNegative values (including -0) are accepted and will work similarly to how \'depth\' usually works. For example, -1 will insert the message right before the last message in chat.',
610 typeList: [ARGUMENT_TYPE.NUMBER],610 typeList: [ARGUMENT_TYPE.NUMBER],
611 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),611 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
612 }),612 }),
@@ -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 },
@@ -3025,7 +3066,7 @@ async function sendUserMessageCallback(args, text) {
3025 let insertAt = Number(args?.at);3066 let insertAt = Number(args?.at);
30263067
3027 // Convert possible depth parameter to index3068 // Convert possible depth parameter to index
3028 if (!isNaN(insertAt) && (insertAt < 0 || insertAt === Number(-0))) {3069 if (!isNaN(insertAt) && (insertAt < 0 || Object.is(insertAt, -0))) {
3029 // Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7)3070 // Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7)
3030 insertAt = chat.length + insertAt;3071 insertAt = chat.length + insertAt;
3031 }3072 }
@@ -3399,7 +3440,7 @@ export async function sendMessageAs(args, text) {
3399 let insertAt = Number(args.at);3440 let insertAt = Number(args.at);
34003441
3401 // Convert possible depth parameter to index3442 // Convert possible depth parameter to index
3402 if (!isNaN(insertAt) && (insertAt < 0 || insertAt === Number(-0))) {3443 if (!isNaN(insertAt) && (insertAt < 0 || Object.is(insertAt, -0))) {
3403 // Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7)3444 // Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7)
3404 insertAt = chat.length + insertAt;3445 insertAt = chat.length + insertAt;
3405 }3446 }
@@ -3453,7 +3494,7 @@ export async function sendNarratorMessage(args, text) {
3453 let insertAt = Number(args.at);3494 let insertAt = Number(args.at);
34543495
3455 // Convert possible depth parameter to index3496 // Convert possible depth parameter to index
3456 if (!isNaN(insertAt) && (insertAt < 0 || insertAt === Number(-0))) {3497 if (!isNaN(insertAt) && (insertAt < 0 || Object.is(insertAt, -0))) {
3457 // Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7)3498 // Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7)
3458 insertAt = chat.length + insertAt;3499 insertAt = chat.length + insertAt;
3459 }3500 }
@@ -3542,7 +3583,7 @@ async function sendCommentMessage(args, text) {
3542 let insertAt = Number(args.at);3583 let insertAt = Number(args.at);
35433584
3544 // Convert possible depth parameter to index3585 // Convert possible depth parameter to index
3545 if (!isNaN(insertAt) && (insertAt < 0 || insertAt === Number(-0))) {3586 if (!isNaN(insertAt) && (insertAt < 0 || Object.is(insertAt, -0))) {
3546 // Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7)3587 // Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7)
3547 insertAt = chat.length + insertAt;3588 insertAt = chat.length + insertAt;
3548 }3589 }
@@ -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/itemizationChat.html+11 -2
@@ -4,8 +4,17 @@
4 <div id="copyPromptToClipboard" class="fa-solid fa-copy menu_button" title="Copy Prompt" data-i18n="[title]Copy Prompt"></div>4 <div id="copyPromptToClipboard" class="fa-solid fa-copy menu_button" title="Copy Prompt" data-i18n="[title]Copy Prompt"></div>
5 <div id="diffPrevPrompt" class="fa-solid fa-code-compare menu_button" title="Show Prompt Differences" data-i18n="[title]Show Prompt Differences"></div>5 <div id="diffPrevPrompt" class="fa-solid fa-code-compare menu_button" title="Show Prompt Differences" data-i18n="[title]Show Prompt Differences"></div>
6</h3>6</h3>
7API/Model Used: {{mainApiFriendlyName}} {{#if apiUsed}}({{apiUsed}}){{/if}} {{#if modelUsed}}&ndash; {{modelUsed}}{{/if}}<br>7<div>
8Tokenizer: {{selectedTokenizer}}<br>8 <div>
9 API/Model: {{mainApiFriendlyName}} {{#if apiUsed}}({{apiUsed}}){{/if}} {{#if modelUsed}}&ndash; {{modelUsed}}{{/if}}
10 </div>
11 <div>
12 <small>Preset: {{presetName}}</small>
13 <span>|</span>
14 <small>Tokenizer: {{selectedTokenizer}}</small>
15 </div>
16</div>
17
9<span class="tokenItemizingSubclass">18<span class="tokenItemizingSubclass">
10 Only the white numbers really matter. All numbers are estimates.19 Only the white numbers really matter. All numbers are estimates.
11 Grey color items may not have been included in the context due to certain prompt format settings.20 Grey color items may not have been included in the context due to certain prompt format settings.
public/scripts/templates/itemizationText.html+11 -2
@@ -4,8 +4,17 @@
4 <div id="copyPromptToClipboard" class="fa-solid fa-copy menu_button" title="Copy Prompt" data-i18n="[title]Copy Prompt"></div>4 <div id="copyPromptToClipboard" class="fa-solid fa-copy menu_button" title="Copy Prompt" data-i18n="[title]Copy Prompt"></div>
5 <div id="diffPrevPrompt" class="fa-solid fa-code-compare menu_button" title="Show Prompt Differences" data-i18n="[title]Show Prompt Differences"></div>5 <div id="diffPrevPrompt" class="fa-solid fa-code-compare menu_button" title="Show Prompt Differences" data-i18n="[title]Show Prompt Differences"></div>
6</h3>6</h3>
7API/Model Used: {{mainApiFriendlyName}} {{#if apiUsed}}({{apiUsed}}){{/if}} {{#if modelUsed}}&ndash; {{modelUsed}}{{/if}}<br>7<div>
8Tokenizer: {{selectedTokenizer}}<br>8 <div>
9 API/Model: {{mainApiFriendlyName}} {{#if apiUsed}}({{apiUsed}}){{/if}} {{#if modelUsed}}&ndash; {{modelUsed}}{{/if}}
10 </div>
11 <div>
12 <small>Preset: {{presetName}}</small>
13 <span>|</span>
14 <small>Tokenizer: {{selectedTokenizer}}</small>
15 </div>
16</div>
17
9<span class="tokenItemizingSubclass">18<span class="tokenItemizingSubclass">
10 Only the white numbers really matter. All numbers are estimates.19 Only the white numbers really matter. All numbers are estimates.
11 Grey color items may not have been included in the context due to certain prompt format settings.20 Grey color items may not have been included in the context due to certain prompt format settings.
public/scripts/templates/macros.html+3 -1
@@ -8,6 +8,7 @@
8 <li><tt>&lcub;&lcub;noop&rcub;&rcub;</tt> – <span data-i18n="help_macros_4">no operation, just an empty string.</span></li>8 <li><tt>&lcub;&lcub;noop&rcub;&rcub;</tt> – <span data-i18n="help_macros_4">no operation, just an empty string.</span></li>
9 <li><tt>&lcub;&lcub;original&rcub;&rcub;</tt> – <span data-i18n="help_macros_5">global prompts defined in API settings. Only valid in Advanced Definitions prompt overrides.</span></li>9 <li><tt>&lcub;&lcub;original&rcub;&rcub;</tt> – <span data-i18n="help_macros_5">global prompts defined in API settings. Only valid in Advanced Definitions prompt overrides.</span></li>
10 <li><tt>&lcub;&lcub;input&rcub;&rcub;</tt> – <span data-i18n="help_macros_6">the user input</span></li>10 <li><tt>&lcub;&lcub;input&rcub;&rcub;</tt> – <span data-i18n="help_macros_6">the user input</span></li>
11 <li><tt>&lcub;&lcub;lastGenerationType&rcub;&rcub;</tt> - <span>the type of the last queued generation request. Empty if no generations were performed yet or the active chat was switched. Possible values: "normal", "impersonate", "regenerate", "quiet", "swipe", "continue".</span></li>
11 <li><tt>&lcub;&lcub;charPrompt&rcub;&rcub;</tt> – <span data-i18n="help_macros_7">the Character's Main Prompt override</span></li>12 <li><tt>&lcub;&lcub;charPrompt&rcub;&rcub;</tt> – <span data-i18n="help_macros_7">the Character's Main Prompt override</span></li>
12 <li><tt>&lcub;&lcub;charInstruction&rcub;&rcub;</tt> – <span data-i18n="help_macros_8">the Character's Post-History Instructions override</span></li>13 <li><tt>&lcub;&lcub;charInstruction&rcub;&rcub;</tt> – <span data-i18n="help_macros_8">the Character's Post-History Instructions override</span></li>
13 <li><tt>&lcub;&lcub;description&rcub;&rcub;</tt> – <span data-i18n="help_macros_9">the Character's Description</span></li>14 <li><tt>&lcub;&lcub;description&rcub;&rcub;</tt> – <span data-i18n="help_macros_9">the Character's Description</span></li>
@@ -20,7 +21,8 @@
20 <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>
21 <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>
22 <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>
23 <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>
24 <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>
25 <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>
26 <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+8 -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', {
@@ -1268,6 +1265,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
12681265
1269 if (settings.type === KOBOLDCPP) {1266 if (settings.type === KOBOLDCPP) {
1270 params.grammar = settings.grammar_string;1267 params.grammar = settings.grammar_string;
1268 params.trim_stop = true;
1271 }1269 }
12721270
1273 if (settings.type === HUGGINGFACE) {1271 if (settings.type === HUGGINGFACE) {
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+66 -23
@@ -191,6 +191,44 @@ function importCAIChat(userName, characterName, jsonData) {
191}191}
192192
193/**193/**
194 * Imports a chat from Kobold Lite format.
195 * @param {string} _userName User name
196 * @param {string} _characterName Character name
197 * @param {object} data JSON data
198 * @returns {string} Chat data
199 */
200function importKoboldLiteChat(_userName, _characterName, data) {
201 const inputToken = '{{[INPUT]}}';
202 const outputToken = '{{[OUTPUT]}}';
203
204 /** @type {function(string): object} */
205 function processKoboldMessage(msg) {
206 const isUser = msg.includes(inputToken);
207 return {
208 name: isUser ? header.user_name : header.character_name,
209 is_user: isUser,
210 mes: msg.replaceAll(inputToken, '').replaceAll(outputToken, '').trim(),
211 send_date: Date.now(),
212 };
213 }
214
215 // Create the header
216 const header = {
217 user_name: String(data.savedsettings.chatname),
218 character_name: String(data.savedsettings.chatopponent).split('||$||')[0],
219 };
220 // Format messages
221 const formattedMessages = data.actions.map(processKoboldMessage);
222 // Add prompt if available
223 if (data.prompt) {
224 formattedMessages.unshift(processKoboldMessage(data.prompt));
225 }
226 // Combine header and messages
227 const chatData = [header, ...formattedMessages];
228 return chatData.map(obj => JSON.stringify(obj)).join('\n');
229}
230
231/**
194 * Flattens `msg` and `swipes` data from Chub Chat format.232 * Flattens `msg` and `swipes` data from Chub Chat format.
195 * Only changes enough to make it compatible with the standard chat serialization format.233 * Only changes enough to make it compatible with the standard chat serialization format.
196 * @param {string} userName User name234 * @param {string} userName User name
@@ -413,7 +451,7 @@ router.post('/import', urlencodedParser, function (request, response) {
413 const format = request.body.file_type;451 const format = request.body.file_type;
414 const avatarUrl = (request.body.avatar_url).replace('.png', '');452 const avatarUrl = (request.body.avatar_url).replace('.png', '');
415 const characterName = request.body.character_name;453 const characterName = request.body.character_name;
416 const userName = request.body.user_name || 'You';454 const userName = request.body.user_name || 'User';
417455
418 if (!request.file) {456 if (!request.file) {
419 return response.sendStatus(400);457 return response.sendStatus(400);
@@ -426,33 +464,38 @@ router.post('/import', urlencodedParser, function (request, response) {
426 if (format === 'json') {464 if (format === 'json') {
427 fs.unlinkSync(pathToUpload);465 fs.unlinkSync(pathToUpload);
428 const jsonData = JSON.parse(data);466 const jsonData = JSON.parse(data);
429 if (jsonData.histories !== undefined) {467
430 // CAI Tools format468 /** @type {function(string, string, object): string|string[]} */
431 const chats = importCAIChat(userName, characterName, jsonData);469 let importFunc;
432 for (const chat of chats) {470
433 const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`;471 if (jsonData.savedsettings !== undefined) { // Kobold Lite format
434 const filePath = path.join(request.user.directories.chats, avatarUrl, fileName);472 importFunc = importKoboldLiteChat;
435 writeFileAtomicSync(filePath, chat, 'utf8');473 } else if (jsonData.histories !== undefined) { // CAI Tools format
436 }474 importFunc = importCAIChat;
437 return response.send({ res: true });475 } else if (Array.isArray(jsonData.data_visible)) { // oobabooga's format
438 } else if (Array.isArray(jsonData.data_visible)) {476 importFunc = importOobaChat;
439 // oobabooga's format477 } else if (Array.isArray(jsonData.messages)) { // Agnai's format
440 const chat = importOobaChat(userName, characterName, jsonData);478 importFunc = importAgnaiChat;
441 const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`;479 } else { // Unknown format
442 const filePath = path.join(request.user.directories.chats, avatarUrl, fileName);480 console.log('Incorrect chat format .json');
443 writeFileAtomicSync(filePath, chat, 'utf8');481 return response.send({ error: true });
444 return response.send({ res: true });482 }
445 } else if (Array.isArray(jsonData.messages)) {483
446 // Agnai format484 const handleChat = (chat) => {
447 const chat = importAgnaiChat(userName, characterName, jsonData);
448 const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`;485 const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`;
449 const filePath = path.join(request.user.directories.chats, avatarUrl, fileName);486 const filePath = path.join(request.user.directories.chats, avatarUrl, fileName);
450 writeFileAtomicSync(filePath, chat, 'utf8');487 writeFileAtomicSync(filePath, chat, 'utf8');
451 return response.send({ res: true });488 };
489
490 const chat = importFunc(userName, characterName, jsonData);
491
492 if (Array.isArray(chat)) {
493 chat.forEach(handleChat);
452 } else {494 } else {
453 console.log('Incorrect chat format .json');495 handleChat(chat);
454 return response.send({ error: true });
455 }496 }
497
498 return response.send({ res: true });
456 }499 }
457500
458 if (format === 'jsonl') {501 if (format === 'jsonl') {
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;