Merge pull request #3156 from SillyTavern/staging Staging

bf4357774dc8ba1171899687181edea071b08221

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

Signed
33 files changed, +600 -304Showing whitespace changes
.eslintrc.cjs+1 -0
@@ -54,6 +54,7 @@ module.exports = {
5454 },
5555 // These scripts are loaded in HTML; tell ESLint not to complain about them being undefined
5656 globals: {
57+ globalThis: 'readonly',
5758 ePub: 'readonly',
5859 pdfjsLib: 'readonly',
5960 toastr: 'readonly',
default/config.yaml+2 -0
@@ -1,6 +1,8 @@
11# -- DATA CONFIGURATION --
22# Root directory for user data storage
33dataRoot: ./data
4+# The maximum amount of memory that parsed character cards can use in MB
5+cardsCacheCapacity: 100
46# -- SERVER CONFIGURATION --
57# Listen for incoming connections
68listen: false
package-lock.json+2 -2
@@ -1,12 +1,12 @@
11{
22 "name": "sillytavern",
33 "version": "1.12.89",
44 "lockfileVersion": 3,
55 "requires": true,
66 "packages": {
77 "": {
88 "name": "sillytavern",
99 "version": "1.12.89",
1010 "hasInstallScript": true,
1111 "license": "AGPL-3.0",
1212 "dependencies": {
package.json+1 -1
@@ -84,7 +84,7 @@
8484 "type": "git",
8585 "url": "https://github.com/SillyTavern/SillyTavern.git"
8686 },
8787 "version": "1.12.89",
8888 "scripts": {
8989 "start": "node server.js",
9090 "start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js",
public/global.d.ts+2 -1
@@ -1,4 +1,5 @@
11import libs from './lib';
2+import getContext from './scripts/st-context';
23
34// Global namespace modules
45declare var ai;
@@ -6,7 +7,7 @@ declare var pdfjsLib;
67declare var ePub;
78
89declare var SillyTavern: {
910 getContext(): anytypeof getContext;
1011 llm: any;
1112 libs: typeof libs;
1213};
public/index.html+8 -7
@@ -669,7 +669,7 @@
669669 </span>
670670 </div>
671671 </div>
672672 <div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt">
673673 <div class="range-block-title" data-i18n="Temperature">
674674 Temperature
675675 </div>
@@ -682,7 +682,7 @@
682682 </div>
683683 </div>
684684 </div>
685685 <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt">
686686 <div class="range-block-title" data-i18n="Frequency Penalty">
687687 Frequency Penalty
688688 </div>
@@ -695,7 +695,7 @@
695695 </div>
696696 </div>
697697 </div>
698698 <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt">
699699 <div class="range-block-title" data-i18n="Presence Penalty">
700700 Presence Penalty
701701 </div>
@@ -721,7 +721,7 @@
721721 </div>
722722 </div>
723723 </div>
724724 <div class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt">
725725 <div class="range-block-title" data-i18n="Top P">
726726 Top P
727727 </div>
@@ -958,7 +958,7 @@
958958 </div>
959959 </div>
960960 </div>
961961 <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,nanogpt">
962962 <div class="range-block-title justifyLeft" data-i18n="Seed">
963963 Seed
964964 </div>
@@ -1425,7 +1425,7 @@
14251425 <input class="neo-range-slider" type="range" id="dry_allowed_length_textgenerationwebui" min="1" max="20" step="1" />
14261426 <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">
14271427 </div>
14281428 <div class="alignItemsCenter flex-container flexFlowColumn flexBasis48p flexGrow flexShrink gap0" data-tg-type="llamacpp, koboldcpp, tabby, aphrodite">
14291429 <small data-i18n="Penalty Range">Penalty Range</small>
14301430 <input class="neo-range-slider" type="range" id="dry_penalty_last_n_textgenerationwebui" min="0" max="8192" step="1" />
14311431 <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 @@
29872987 <optgroup label="Subversions">
29882988 <option value="gemini-exp-1121">Gemini Experimental 2024-11-21</option>
29892989 <option value="gemini-exp-1114">Gemini Experimental 2024-11-14</option>
2990+ <option value="gemini-exp-1206">Gemini Experimental 2024-12-06</option>
29902991 <option value="gemini-1.5-pro-exp-0801">Gemini 1.5 Pro Experiment 2024-08-01</option>
29912992 <option value="gemini-1.5-pro-exp-0827">Gemini 1.5 Pro Experiment 2024-08-27</option>
29922993 <option value="gemini-1.5-pro-latest">Gemini 1.5 Pro [latest]</option>
@@ -4183,7 +4184,7 @@
41834184 </label>
41844185 <label for="show_swipe_num_all_messages" class="checkbox_label" title="Display swipe numbers for all messages, not just the last." data-i18n="[title]Display swipe numbers for all messages, not just the last.">
41854186 <input id="show_swipe_num_all_messages" type="checkbox" />
41864187 <small data-i18n="Swipe # for All Messages">Swipe # for All Messages</small><i class="fa-solid fa-mobile-screen-button"></i>
41874188 </label>
41884189 <label for="hotswapEnabled" class="checkbox_label" title="In the Character Management panel, show quick selection buttons for favorited characters." data-i18n="[title]In the Character Management panel, show quick selection buttons for favorited characters">
41894190 <input id="hotswapEnabled" type="checkbox" />
public/jsconfig.json+2 -1
@@ -5,7 +5,8 @@
55 "module": "ESNext",
66 "moduleResolution": "node",
77 "allowUmdGlobalAccess": true,
88 "allowSyntheticDefaultImports": true,
9+ "strictBindCallApply": true
910 },
1011 "exclude": [
1112 "**/node_modules/**",
public/script.js+74 -161
@@ -13,7 +13,7 @@ import {
1313 default as libs,
1414} from './lib.js';
1515
1616import { humanizedDateTime, favsToHotswap, getMessageTimeStamp, dragElement, isMobile, initRossMods, shouldSendOnEnter } from './scripts/RossAscends-mods.js';
1717import { userStatsHandler, statMesProcess, initStats } from './scripts/stats.js';
1818import {
1919 generateKoboldWithStreaming,
@@ -37,8 +37,6 @@ import {
3737 parseTabbyLogprobs,
3838} from './scripts/textgen-settings.js';
3939
40-const { MANCER, TOGETHERAI, OOBA, VLLM, APHRODITE, TABBY, OLLAMA, INFERMATICAI, DREAMGEN, OPENROUTER, FEATHERLESS } = textgen_types;
41-
4240import {
4341 world_info,
4442 getWorldInfoPrompt,
@@ -67,9 +65,7 @@ import {
6765 getGroupChat,
6866 renameGroupMember,
6967 createNewGroupChat,
70- getGroupPastChats,
7168 getGroupAvatar,
72- openGroupChat,
7369 editGroup,
7470 deleteGroupChat,
7571 renameGroupChat,
@@ -98,7 +94,6 @@ import {
9894 resetMovableStyles,
9995 forceCharacterEditorTokenize,
10096 applyPowerUserSettings,
101- switchSwipeNumAllMessages,
10297} from './scripts/power-user.js';
10398
10499import {
@@ -174,8 +169,8 @@ import {
174169} from './scripts/utils.js';
175170import { debounce_timeout } from './scripts/constants.js';
176171
177172import { ModuleWorkerWrapper, doDailyExtensionUpdatesCheck, extension_settings, getContext, initExtensions, loadExtensionSettings, renderExtensionTemplate, renderExtensionTemplateAsync, runGenerationInterceptors, saveMetadataDebounced, writeExtensionField } from './scripts/extensions.js';
178173import { COMMENT_NAME_DEFAULT, executeSlashCommands, executeSlashCommandsOnChatInput, executeSlashCommandsWithOptions, getSlashCommandsHelp, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, processChatSlashCommands, registerSlashCommand, stopScriptExecution } from './scripts/slash-commands.js';
179174import {
180175 tag_map,
181176 tags,
@@ -225,8 +220,8 @@ import {
225220 instruct_presets,
226221 selectContextPreset,
227222} from './scripts/instruct-mode.js';
228223import { initLocales, t, translate } from './scripts/i18n.js';
229224import { getFriendlyTokenizerName, getTokenCount, getTokenCountAsync, getTokenizerModel, initTokenizers, saveTokenCache, TOKENIZER_SUPPORTED_KEY } from './scripts/tokenizers.js';
230225import {
231226 user_avatar,
232227 getUserAvatars,
@@ -241,12 +236,12 @@ import { hideLoader, showLoader } from './scripts/loader.js';
241236import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js';
242237import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels, loadTabbyModels } from './scripts/textgen-models.js';
243238import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId, preserveNeutralChat, restoreNeutralChat } from './scripts/chats.js';
244239import { getPresetManager, initPresetManager } from './scripts/preset-manager.js';
245240import { MacrosParser, evaluateMacros, getLastMessageId, initMacros } from './scripts/macros.js';
246241import { currentUser, setUserControls } from './scripts/user.js';
247242import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js';
248243import { renderTemplate, renderTemplateAsync } from './scripts/templates.js';
249244import { initScrapers, ScraperManager } from './scripts/scrapers.js';
250245import { SlashCommandParser } from './scripts/slash-commands/SlashCommandParser.js';
251246import { SlashCommand } from './scripts/slash-commands/SlashCommand.js';
252247import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './scripts/slash-commands/SlashCommandArgument.js';
@@ -268,6 +263,13 @@ import { initServerHistory } from './scripts/server-history.js';
268263import { initSettingsSearch } from './scripts/setting-search.js';
269264import { initBulkEdit } from './scripts/bulk-edit.js';
270265import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js';
266+import { getContext } from './scripts/st-context.js';
267+
268+// API OBJECT FOR EXTERNAL WIRING
269+globalThis.SillyTavern = {
270+ libs,
271+ getContext,
272+};
271273
272274//exporting functions and vars for mods
273275export {
@@ -427,10 +429,6 @@ DOMPurify.addHook('uponSanitizeElement', (node, _, config) => {
427429 }
428430});
429431
430-// API OBJECT FOR EXTERNAL WIRING
431-window['SillyTavern'] = {};
432-window['SillyTavern'].libs = libs;
433-
434432// Event source init
435433export const event_types = {
436434 APP_READY: 'app_ready',
@@ -536,7 +534,7 @@ let displayVersion = 'SillyTavern';
536534
537535let generatedPromptCache = '';
538536let generation_started = new Date();
539537/** @type {import('./scripts/char-data.js').v1CharData[]} */
540538export let characters = [];
541539export let this_chid;
542540let saveCharactersPage = 0;
@@ -812,7 +810,7 @@ export let menu_type = '';
812810export let selected_button = ''; //which button pressed
813811
814812//create pole save
815813export let create_save = {
816814 name: '',
817815 description: '',
818816 creator_notes: '',
@@ -864,7 +862,7 @@ export let amount_gen = 80; //default max length of AI generated responses
864862export let max_context = 2048;
865863
866864var swipes = true;
867865export let extension_prompts = {};
868866
869867export let main_api;// = "kobold";
870868//novel settings
@@ -957,6 +955,7 @@ async function firstLoadInit() {
957955 initDynamicStyles();
958956 initTags();
959957 initBookmarks();
958+ initMacros();
960959 await getUserAvatars(true, user_avatar);
961960 await getCharacters();
962961 await getBackgrounds();
@@ -1174,7 +1173,7 @@ async function getStatusTextgen() {
11741173 return resultCheckStatus();
11751174 }
11761175
11771176 if (textgen_settings.type == textgen_types.OOBA && textgen_settings.bypass_status_check) {
11781177 setOnlineStatus('Status check bypassed');
11791178 return resultCheckStatus();
11801179 }
@@ -1192,34 +1191,34 @@ async function getStatusTextgen() {
11921191
11931192 const data = await response.json();
11941193
11951194 if (textgen_settings.type === textgen_types.MANCER) {
11961195 loadMancerModels(data?.data);
11971196 setOnlineStatus(textgen_settings.mancer_model);
11981197 } else if (textgen_settings.type === textgen_types.TOGETHERAI) {
11991198 loadTogetherAIModels(data?.data);
12001199 setOnlineStatus(textgen_settings.togetherai_model);
12011200 } else if (textgen_settings.type === textgen_types.OLLAMA) {
12021201 loadOllamaModels(data?.data);
12031202 setOnlineStatus(textgen_settings.ollama_model || 'Connected');
12041203 } else if (textgen_settings.type === textgen_types.INFERMATICAI) {
12051204 loadInfermaticAIModels(data?.data);
12061205 setOnlineStatus(textgen_settings.infermaticai_model);
12071206 } else if (textgen_settings.type === textgen_types.DREAMGEN) {
12081207 loadDreamGenModels(data?.data);
12091208 setOnlineStatus(textgen_settings.dreamgen_model);
12101209 } else if (textgen_settings.type === textgen_types.OPENROUTER) {
12111210 loadOpenRouterModels(data?.data);
12121211 setOnlineStatus(textgen_settings.openrouter_model);
12131212 } else if (textgen_settings.type === textgen_types.VLLM) {
12141213 loadVllmModels(data?.data);
12151214 setOnlineStatus(textgen_settings.vllm_model);
12161215 } else if (textgen_settings.type === textgen_types.APHRODITE) {
12171216 loadAphroditeModels(data?.data);
12181217 setOnlineStatus(textgen_settings.aphrodite_model);
12191218 } else if (textgen_settings.type === textgen_types.FEATHERLESS) {
12201219 loadFeatherlessModels(data?.data);
12211220 setOnlineStatus(textgen_settings.featherless_model);
12221221 } else if (textgen_settings.type === textgen_types.TABBY) {
12231222 loadTabbyModels(data?.data);
12241223 setOnlineStatus(textgen_settings.tabby_model || data?.result);
12251224 } else {
@@ -1636,7 +1635,7 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) {
16361635 subEntities = entitiesFilter.applyFilters(subEntities, { clearScoreCache: false, tempOverrides: { [FILTER_TYPES.FOLDER]: FILTER_STATES.UNDEFINED }, clearFuzzySearchCaches: false });
16371636 }
16381637 if (doSort) {
16391638 sortEntitiesList(subEntities, false);
16401639 }
16411640 entity.entities = subEntities;
16421641 entity.hidden = subCount - subEntities.length;
@@ -1664,7 +1663,7 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) {
16641663
16651664 // Sort before returning if requested
16661665 if (doSort) {
16671666 sortEntitiesList(entities, false);
16681667 }
16691668 entitiesFilter.clearFuzzySearchCaches();
16701669 return entities;
@@ -2606,15 +2605,18 @@ export function substituteParams(content, _name1, _name2, _original, _group, _re
26062605 };
26072606 }
26082607
26092608 const getGroupValue = (includeMuted) => {
26102609 if (typeof _group === 'string') {
26112610 return _group;
26122611 }
26132612
26142613 if (selected_group) {
26152614 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);
26162618 const names = Array.isArray(members)
26172619 ? members.filter(isMuted).map(m => characters.find(c => c.avatar === m)?.name).filter(Boolean).join(', ')
26182620 : '';
26192621 return names;
26202622 } else {
@@ -2638,7 +2640,8 @@ export function substituteParams(content, _name1, _name2, _original, _group, _re
26382640 // Must be substituted last so that they're replaced inside {{description}}
26392641 environment.user = _name1 ?? name1;
26402642 environment.char = _name2 ?? name2;
26412643 environment.group = environment.charIfNotGroup = getGroupValue(true);
2644+ environment.groupNotMuted = getGroupValue(false);
26422645 environment.model = getGeneratingModel();
26432646
26442647 if (additionalMacro && typeof additionalMacro === 'object') {
@@ -4493,6 +4496,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
44934496 instruction: main_api !== 'openai' && power_user.sysprompt.enabled ? substituteParams(power_user.prefer_character_prompt && system ? system : power_user.sysprompt.content) : '',
44944497 userPersona: (power_user.persona_description_position == persona_description_positions.IN_PROMPT ? (persona || '') : ''),
44954498 tokenizer: getFriendlyTokenizerName(main_api).tokenizerName || '',
4499+ presetName: getPresetManager()?.getSelectedPresetName() || '',
44964500 };
44974501
44984502 //console.log(additionalPromptStuff);
@@ -5159,6 +5163,7 @@ export async function itemizedParams(itemizedPrompts, thisPromptSet, incomingMes
51595163 dataBankVectorsStringTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].dataBankVectorsString),
51605164 modelUsed: chat[incomingMesId]?.extra?.model,
51615165 apiUsed: chat[incomingMesId]?.extra?.api,
5166+ presetName: itemizedPrompts[thisPromptSet].presetName || t`(Unknown)`,
51625167 };
51635168
51645169 const getFriendlyName = (value) => $(`#rm_api_block select option[value="${value}"]`).first().text() || value;
@@ -5535,7 +5540,7 @@ function extractMultiSwipes(data, type) {
55355540 return swipes;
55365541 }
55375542
55385543 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))) {
55395544 if (!Array.isArray(data.choices)) {
55405545 return swipes;
55415546 }
@@ -5698,7 +5703,7 @@ export function cleanUpMessage(getMessage, isImpersonate, isContinue, displayInc
56985703 return getMessage;
56995704}
57005705
57015706export async function saveReply(type, getMessage, fromStreaming, title, swipes) {
57025707 if (type != 'append' && type != 'continue' && type != 'appendFinal' && chat.length && (chat[chat.length - 1]['swipe_id'] === undefined ||
57035708 chat[chat.length - 1]['is_user'])) {
57045709 type = 'normal';
@@ -7657,14 +7662,12 @@ export function showSwipeButtons() {
76577662 //allows for writing individual swipe counters for past messages
76587663 const lastSwipeCounter = $('.last_mes .swipes-counter');
76597664 lastSwipeCounter.text(swipeCounterText).show();
7660-
7661- switchSwipeNumAllMessages();
76627665}
76637666
76647667export function hideSwipeButtons() {
76657668 $('#chat')chatElement.find('.swipe_right').hide();
76667669 $('#chat')chatElement.find('.last_mes .swipes-counter').hide();
76677670 $('#chat')chatElement.find('.swipe_left').hide();
76687671}
76697672
76707673/**
@@ -7751,25 +7754,31 @@ export async function saveChatConditional() {
77517754 }
77527755}
77537756
7754-async 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+ */
77597762 beforeSend:async 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', {
77637766 processDatamethod: false'POST',
7764- success: async function (data) {
7767+ body: formData,
7768+ headers: headers,
7769+ cache: 'no-cache',
7770+ });
7771+
7772+ if (fetchResult.ok) {
7773+ const data = await fetchResult.json();
77657774 if (data.res) {
77667775 await displayPastChats();
77677776 }
7768- },
7777+ }
7769- error: function () {
7778+
7770- $('#create_button').removeAttr('disabled');
7779+ if (eventTarget instanceof HTMLInputElement) {
7771- },
7780+ eventTarget.value = '';
77727781 });
77737782}
77747783
77757784function updateViewMessageIds(startFromZero = false) {
@@ -8224,102 +8233,6 @@ async function createOrEditCharacter(e) {
82248233 }
82258234}
82268235
8227-window['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-
83238236/**
83248237 * Formats a counter for a swipe view.
83258238 * @param {number} current The current number of items.
@@ -10826,13 +10739,13 @@ jQuery(async function () {
1082610739 });
1082710740
1082810741 $('#chat_import_file').on('change', async function (e) {
1082910742 varconst file = e.target.files[0];
1083010743
1083110744 if (!file) {
1083210745 return;
1083310746 }
1083410747
1083510748 varconst ext = file.name.match(/\.(\w+)$/);
1083610749 if (
1083710750 !ext ||
1083810751 (ext[1].toLowerCase() != 'json' && ext[1].toLowerCase() != 'jsonl')
@@ -10845,17 +10758,17 @@ jQuery(async function () {
1084510758 return;
1084610759 }
1084710760
1084810761 varconst format = ext[1].toLowerCase();
1084910762 $('#chat_import_file_type').val(format);
1085010763
1085110764 varconst formData = new FormData($('#form_import_chat').get(0));
1085210765 formData.append('user_name', name1);
1085310766 $('#select_chat_div').html('');
1085410767
1085510768 if (selected_group) {
1085610769 await importGroupChat(formData, e.originalEvent.target);
1085710770 } else {
1085810771 await importCharacterChat(formData, e.originalEvent.target);
1085910772 }
1086010773 });
1086110774
public/scripts/char-data.js+1 -0
@@ -75,6 +75,7 @@
7575 * @property {string} [source_url] - The source URL associated with the character.
7676 * @property {{full_path: string}} [chub] - The Chub-specific data associated with the character.
7777 * @property {{source: string[]}} [risuai] - The RisuAI-specific data associated with the character.
78+ * @property {{positive: string, negative: string}} [sd_character_prompt] - SD-specific data associated with the character.
7879 */
7980
8081/**
public/scripts/extensions.js+1 -1
@@ -5,6 +5,7 @@ import { showLoader } from './loader.js';
55import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
66import { renderTemplate, renderTemplateAsync } from './templates.js';
77import { isSubsetOf, setValueByPath } from './utils.js';
8+import { getContext } from './st-context.js';
89export {
910 getContext,
1011 getApiUrl,
@@ -174,7 +175,6 @@ const extension_settings = {
174175let modules = [];
175176let activeExtensions = new Set();
176177
177-const getContext = () => window['SillyTavern'].getContext();
178178const getApiUrl = () => extension_settings.apiUrl;
179179let connectedToApi = false;
180180
public/scripts/extensions/caption/settings.html+1 -0
@@ -62,6 +62,7 @@
6262 <option data-type="google" value="gemini-1.5-flash-8b-exp-0924">gemini-1.5-flash-8b-exp-0924</option>
6363 <option data-type="google" value="gemini-exp-1114">gemini-exp-1114</option>
6464 <option data-type="google" value="gemini-exp-1121">gemini-exp-1121</option>
65+ <option data-type="google" value="gemini-exp-1206">gemini-exp-1206</option>
6566 <option data-type="google" value="gemini-1.5-pro">gemini-1.5-pro</option>
6667 <option data-type="google" value="gemini-1.5-pro-latest">gemini-1.5-pro-latest</option>
6768 <option data-type="google" value="gemini-1.5-pro-001">gemini-1.5-pro-001</option>
public/scripts/extensions/stable-diffusion/index.js+0 -15
@@ -60,7 +60,6 @@ import { ToolManager } from '../../tool-calling.js';
6060export { MODULE_NAME };
6161
6262const MODULE_NAME = 'sd';
63-const UPDATE_INTERVAL = 1000;
6463// This is a 1x1 transparent PNG
6564const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
6665const CUSTOM_STOP_EVENT = 'sd_stop_generation';
@@ -3687,8 +3686,6 @@ async function addSDGenButtons() {
36873686 const button = $('#sd_gen');
36883687 const dropdown = $('#sd_dropdown');
36893688 dropdown.hide();
3690- button.hide();
3691- messageButton.hide();
36923689
36933690 let popper = Popper.createPopper(button.get(0), dropdown.get(0), {
36943691 placement: 'top',
@@ -3770,18 +3767,6 @@ function isValidState() {
37703767 }
37713768}
37723769
3773-async function moduleWorker() {
3774- if (isValidState()) {
3775- $('#sd_gen').show();
3776- $('.sd_message_gen').show();
3777- }
3778- else {
3779- $('#sd_gen').hide();
3780- $('.sd_message_gen').hide();
3781- }
3782-}
3783-
3784-setInterval(moduleWorker, UPDATE_INTERVAL);
37853770let buttonAbortController = null;
37863771
37873772async function sdMessageButton(e) {
public/scripts/extensions/stable-diffusion/style.css+1 -1
@@ -4,7 +4,7 @@
44
55#sd_dropdown {
66 z-index: 30000;
77 backdrop-filter: blur(var(--SmartThemeBlurStrength));
88}
99
1010#sd_comfy_open_workflow_editor {
public/scripts/extensions/tts/style.css+1 -1
@@ -93,7 +93,7 @@
9393.at-settings-separator {
9494 margin-top: 10px;
9595 margin-bottom: 10px;
9696 padding: 18x18px;
9797 font-weight: bold;
9898 border-top: 1px solid #e1e1e1; /* Grey line */
9999 border-bottom: 1px solid #e1e1e1; /* Grey line */
public/scripts/group-chats.js+30 -22
@@ -1265,18 +1265,20 @@ function getGroupCharacters({ doFilter, onlyMembers } = {}) {
12651265 const thisGroup = openGroupId && groups.find((x) => x.id == openGroupId);
12661266 let candidates = characters
12671267 .filter((x) => isGroupMember(thisGroup, x.avatar) == onlyMembers)
12681268 .map((x, index) => ({ item: x, id: indexcharacters.indexOf(x), type: 'character' }));
1269+
1270+ if (doFilter) {
1271+ candidates = groupCandidatesFilter.applyFilters(candidates);
1272+ }
12691273
12701274 if (onlyMembers) {
12711275 candidates.sort(sortMembersFn);
12721276 } else {
1273- sortEntitiesList(candidates);
1277+ const useFilterOrder = doFilter && !!$('#rm_group_filter').val();
1274- }
1278+ sortEntitiesList(candidates, useFilterOrder, groupCandidatesFilter);
1275-
1276- if (doFilter) {
1277- candidates = groupCandidatesFilter.applyFilters(candidates);
12781279 }
12791280
1281+ groupCandidatesFilter.clearFuzzySearchCaches();
12801282 return candidates;
12811283}
12821284
@@ -1863,17 +1865,23 @@ export async function deleteGroupChat(groupId, chatId) {
18631865 }
18641866}
18651867
1866-export 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+ */
18711873 beforeSend:export 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', {
18751877 processDatamethod: false'POST',
1876- success: async function (data) {
1878+ headers: headers,
1879+ body: formData,
1880+ cache: 'no-cache',
1881+ });
1882+
1883+ if (fetchResult.ok) {
1884+ const data = await fetchResult.json();
18771885 if (data.res) {
18781886 const chatId = data.res;
18791887 const group = groups.find(x => x.id == selected_group);
@@ -1884,11 +1892,11 @@ export async function importGroupChat(formData) {
18841892 await displayPastChats();
18851893 }
18861894 }
1887- },
1895+ }
1888- error: function () {
1896+
1889- $('#create_button').removeAttr('disabled');
1897+ if (eventTarget instanceof HTMLInputElement) {
1890- },
1898+ eventTarget.value = '';
18911899 });
18921900}
18931901
18941902export async function saveGroupBookmarkChat(groupId, name, metadata, mesId) {
public/scripts/macros.js+20 -1
@@ -1,5 +1,5 @@
11import { Handlebars, moment, seedrandom, droll } from '../lib.js';
22import { chat, chat_metadata, main_api, getMaxContextSize, getCurrentChatId, substituteParams, eventSource, event_types } from '../script.js';
33import { timestampToMoment, isDigitsOnly, getStringHash, escapeRegex, uuidv4 } from './utils.js';
44import { textgenerationwebui_banned_in_macros } from './textgen-settings.js';
55import { getInstructMacros } from './instruct-mode.js';
@@ -521,3 +521,22 @@ export function evaluateMacros(content, env, postProcessFn) {
521521
522522 return content;
523523}
524+
525+export 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];
379379let openai_setting_names;
380380let openai_settings;
381381
382-
382+/** @type {import('./PromptManager.js').PromptManager} */
383383export let promptManager = null;
384384
385385async function validateReverseProxy() {
386386 if (!oai_settings.reverse_proxy) {
@@ -1812,6 +1812,7 @@ async function sendOpenAIRequest(type, messages, signal) {
18121812 const isPerplexity = oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY;
18131813 const isGroq = oai_settings.chat_completion_source == chat_completion_sources.GROQ;
18141814 const is01AI = oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI;
1815+ const isNano = oai_settings.chat_completion_source == chat_completion_sources.NANOGPT;
18151816 const isTextCompletion = isOAI && textCompletionModels.includes(oai_settings.openai_model);
18161817 const isQuiet = type === 'quiet';
18171818 const isImpersonate = type === 'impersonate';
@@ -1971,7 +1972,7 @@ async function sendOpenAIRequest(type, messages, signal) {
19711972 delete generate_data.stop;
19721973 }
19731974
19741975 if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere || isNano) && oai_settings.seed >= 0) {
19751976 generate_data['seed'] = oai_settings.seed;
19761977 }
19771978
@@ -4076,7 +4077,7 @@ async function onModelChange() {
40764077 $('#openai_max_context').attr('max', max_2mil);
40774078 } else if (value.includes('gemini-exp-1114') || value.includes('gemini-exp-1121')) {
40784079 $('#openai_max_context').attr('max', max_32k);
40794080 } else if (value.includes('gemini-1.5-pro') || value.includes('gemini-exp-1206')) {
40804081 $('#openai_max_context').attr('max', max_2mil);
40814082 } else if (value.includes('gemini-1.5-flash')) {
40824083 $('#openai_max_context').attr('max', max_1mil);
@@ -4760,6 +4761,7 @@ export function isImageInliningSupported() {
47604761 'gemini-1.5-flash-8b-exp-0924',
47614762 'gemini-exp-1114',
47624763 'gemini-exp-1121',
4764+ 'gemini-exp-1206',
47634765 'gemini-1.0-pro-vision-latest',
47644766 'gemini-1.5-pro',
47654767 'gemini-1.5-pro-latest',
public/scripts/power-user.js+13 -12
@@ -60,7 +60,6 @@ export {
6060 loadMovingUIState,
6161 collapseNewlines,
6262 playMessageSound,
63- sortEntitiesList,
6463 fixMarkdown,
6564 power_user,
6665 send_on_enter_options,
@@ -473,9 +472,9 @@ function switchCompactInputArea() {
473472 $('#compact_input_area').prop('checked', power_user.compact_input_area);
474473}
475474
476475export function switchSwipeNumAllMessages() {
477476 $('#show_swipe_num_all_messages').prop('checked', power_user.show_swipe_num_all_messages);
478477 $('.mes:not(.last_mes) .swipes-counterbody').csstoggleClass('opacityswipeAllMessages', '').toggle(!!power_user.show_swipe_num_all_messages);
479478}
480479
481480var originalSliderValues = [];
@@ -1837,7 +1836,7 @@ async function loadContextSettings() {
18371836 * Common function to perform fuzzy search with optional caching
18381837 * @param {string} type - Type of search from fuzzySearchCategories
18391838 * @param {any[]} data - Data array to search in
18401839 * @param {Array<{name: string, weight: number, getFn?: Function(obj: any) => string}>} keys - Fuse.js keys configuration
18411840 * @param {string} searchValue - The search term
18421841 * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
18431842 * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score
@@ -1851,7 +1850,6 @@ function performFuzzySearch(type, data, keys, searchValue, fuzzySearchCaches = n
18511850 }
18521851 }
18531852
1854- // @ts-ignore
18551853 const fuse = new Fuse(data, {
18561854 keys: keys,
18571855 includeScore: true,
@@ -1925,7 +1923,7 @@ export function fuzzySearchPersonas(data, searchValue, fuzzySearchCaches = null)
19251923 const mappedData = data.map(x => ({
19261924 key: x,
19271925 name: power_user.personas[x] ?? '',
19281926 description: power_user.persona_descriptions[x]?.description ?? '',
19291927 }));
19301928
19311929 const keys = [
@@ -2080,19 +2078,22 @@ const compareFunc = (first, second) => {
20802078/**
20812079 * Sorts an array of entities based on the current sort settings
20822080 * @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
20832083 */
20842084export function sortEntitiesList(entities, forceSearch, filterHelper = null) {
2085+ filterHelper = filterHelper ?? entitiesFilter;
20852086 if (power_user.sort_field == undefined || entities.length === 0) {
20862087 return;
20872088 }
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') {
20902093 shuffle(entities);
20912094 return;
20922095 }
20932096
2094- const isSearch = $('#character_sort_order option[data-field="search"]').is(':selected');
2095-
20962097 entities.sort((a, b) => {
20972098 // Sort tags/folders will always be at the top
20982099 if (a.type === 'tag' && b.type !== 'tag') {
@@ -2104,8 +2105,8 @@ function sortEntitiesList(entities) {
21042105
21052106 // If we have search sorting, we take scores and use those
21062107 if (isSearch) {
21072108 const aScore = entitiesFilterfilterHelper.getScore(FILTER_TYPES.SEARCH, `${a.type}.${a.id}`);
21082109 const bScore = entitiesFilterfilterHelper.getScore(FILTER_TYPES.SEARCH, `${b.type}.${b.id}`);
21092110 return (aScore - bScore);
21102111 }
21112112
public/scripts/slash-commands.js+121 -12
@@ -52,7 +52,7 @@ import { hideChatMessageRange } from './chats.js';
5252import { getContext, saveMetadataDebounced } from './extensions.js';
5353import { getRegexedString, regex_placement } from './extensions/regex/engine.js';
5454import { findGroupMemberId, groups, is_group_generating, openGroupById, resetSelectedGroup, saveGroupChat, selected_group } from './group-chats.js';
5555import { chat_completion_sources, oai_settings, setupChatCompletionPromptManagerpromptManager } from './openai.js';
5656import { autoSelectPersona, retriggerFirstMessageOnEmptyChat, setPersonaLockState, togglePersonaLock, user_avatar } from './personas.js';
5757import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js';
5858import { SERVER_INPUTS, textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
@@ -269,7 +269,7 @@ export function initDefaultSlashCommands() {
269269 }),
270270 SlashCommandNamedArgument.fromProps({
271271 name: 'at',
272272 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.',
273273 typeList: [ARGUMENT_TYPE.NUMBER],
274274 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
275275 }),
@@ -325,7 +325,7 @@ export function initDefaultSlashCommands() {
325325 ),
326326 SlashCommandNamedArgument.fromProps({
327327 name: 'at',
328328 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.',
329329 typeList: [ARGUMENT_TYPE.NUMBER],
330330 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
331331 }),
@@ -388,7 +388,7 @@ export function initDefaultSlashCommands() {
388388 ),
389389 SlashCommandNamedArgument.fromProps({
390390 name: 'at',
391391 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.',
392392 typeList: [ARGUMENT_TYPE.NUMBER],
393393 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
394394 }),
@@ -606,7 +606,7 @@ export function initDefaultSlashCommands() {
606606 ),
607607 SlashCommandNamedArgument.fromProps({
608608 name: 'at',
609609 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.',
610610 typeList: [ARGUMENT_TYPE.NUMBER],
611611 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
612612 }),
@@ -1699,6 +1699,49 @@ export function initDefaultSlashCommands() {
16991699 helpString: 'Sets the model for the current API. Gets the current model name if no argument is provided.',
17001700 }));
17011701 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1702+ name: 'getpromptentry',
1703+ aliases: ['getpromptentries'],
1704+ callback: getPromptEntryCallback,
1705+ returns: 'true/false state of prompt(s)',
1706+ namedArgumentList: [
1707+ SlashCommandNamedArgument.fromProps({
1708+ name: 'identifier',
1709+ description: 'Prompt entry identifier(s) to retrieve',
1710+ typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST],
1711+ acceptsMultiple: true,
1712+ enumProvider: () =>
1713+ promptManager.serviceSettings.prompts
1714+ .map(prompt => prompt.identifier)
1715+ .map(identifier => new SlashCommandEnumValue(identifier)),
1716+ }),
1717+ SlashCommandNamedArgument.fromProps({
1718+ name: 'name',
1719+ description: 'Prompt entry name(s) to retrieve',
1720+ typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST],
1721+ acceptsMultiple: true,
1722+ enumProvider: () =>
1723+ promptManager.serviceSettings.prompts
1724+ .map(prompt => prompt.name)
1725+ .map(name => new SlashCommandEnumValue(name)),
1726+ }),
1727+ SlashCommandNamedArgument.fromProps({
1728+ name: 'return',
1729+ description: 'Whether the return will be simple, a list, or a dict.',
1730+ typeList: [ARGUMENT_TYPE.STRING],
1731+ defaultValue: 'simple',
1732+ enumList: ['simple', 'list', 'dict'],
1733+ }),
1734+ ],
1735+ helpString: `
1736+ <div>
1737+ Gets the state of the specified prompt entries.
1738+ </div>
1739+ <div>
1740+ If <code>return</code> is <code>simple</code> (default) then the return will be a single value if only one value was retrieved; otherwise uses a dict (if the identifier parameter was used) or a list.
1741+ </div>
1742+ `,
1743+ }));
1744+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
17021745 name: 'setpromptentry',
17031746 aliases: ['setpromptentries'],
17041747 callback: setPromptEntryCallback,
@@ -1709,7 +1752,6 @@ export function initDefaultSlashCommands() {
17091752 typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST],
17101753 acceptsMultiple: true,
17111754 enumProvider: () => {
1712- const promptManager = setupChatCompletionPromptManager(oai_settings);
17131755 const prompts = promptManager.serviceSettings.prompts;
17141756 return prompts.map(prompt => new SlashCommandEnumValue(prompt.identifier, prompt.name, enumTypes.enum));
17151757 },
@@ -1720,7 +1762,6 @@ export function initDefaultSlashCommands() {
17201762 typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST],
17211763 acceptsMultiple: true,
17221764 enumProvider: () => {
1723- const promptManager = setupChatCompletionPromptManager(oai_settings);
17241765 const prompts = promptManager.serviceSettings.prompts;
17251766 return prompts.map(prompt => new SlashCommandEnumValue(prompt.name, prompt.identifier, enumTypes.enum));
17261767 },
@@ -3025,7 +3066,7 @@ async function sendUserMessageCallback(args, text) {
30253066 let insertAt = Number(args?.at);
30263067
30273068 // Convert possible depth parameter to index
30283069 if (!isNaN(insertAt) && (insertAt < 0 || Object.is(insertAt ===, Number(-0))) {
30293070 // Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7)
30303071 insertAt = chat.length + insertAt;
30313072 }
@@ -3399,7 +3440,7 @@ export async function sendMessageAs(args, text) {
33993440 let insertAt = Number(args.at);
34003441
34013442 // Convert possible depth parameter to index
34023443 if (!isNaN(insertAt) && (insertAt < 0 || Object.is(insertAt ===, Number(-0))) {
34033444 // Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7)
34043445 insertAt = chat.length + insertAt;
34053446 }
@@ -3453,7 +3494,7 @@ export async function sendNarratorMessage(args, text) {
34533494 let insertAt = Number(args.at);
34543495
34553496 // Convert possible depth parameter to index
34563497 if (!isNaN(insertAt) && (insertAt < 0 || Object.is(insertAt ===, Number(-0))) {
34573498 // Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7)
34583499 insertAt = chat.length + insertAt;
34593500 }
@@ -3542,7 +3583,7 @@ async function sendCommentMessage(args, text) {
35423583 let insertAt = Number(args.at);
35433584
35443585 // Convert possible depth parameter to index
35453586 if (!isNaN(insertAt) && (insertAt < 0 || Object.is(insertAt ===, Number(-0))) {
35463587 // Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7)
35473588 insertAt = chat.length + insertAt;
35483589 }
@@ -3787,6 +3828,75 @@ function modelCallback(args, model) {
37873828}
37883829
37893830/**
3831+ * Gets the state of prompt entries (toggles) either via identifier/uuid or name.
3832+ * @param {object} args Object containing arguments
3833+ * @param {string} args.identifier Select prompt entry using an identifier (uuid)
3834+ * @param {string} args.name Select prompt entry using name
3835+ * @param {string} args.return The type of return value to use (simple, list, dict)
3836+ * @returns {Object} An object containing the states of the requested prompt entries
3837+ */
3838+function getPromptEntryCallback(args) {
3839+ const prompts = promptManager.serviceSettings.prompts;
3840+ let returnType = args.return ?? 'simple';
3841+
3842+ function parseArgs(arg) {
3843+ // Arg is already an array
3844+ if (Array.isArray(arg)) {
3845+ return arg;
3846+ }
3847+ const list = [];
3848+ try {
3849+ // Arg is a JSON-stringified array
3850+ const parsedArg = JSON.parse(arg);
3851+ list.push(...Array.isArray(parsedArg) ? parsedArg : [arg]);
3852+ } catch {
3853+ // Arg is a string
3854+ list.push(arg);
3855+ }
3856+ return list;
3857+ }
3858+
3859+ let identifiersList = parseArgs(args.identifier);
3860+ let nameList = parseArgs(args.name);
3861+
3862+ // Check if identifiers exists in prompt, else remove from list
3863+ if (identifiersList.length !== 0) {
3864+ identifiersList = identifiersList.filter(identifier => prompts.some(prompt => prompt.identifier === identifier));
3865+ }
3866+
3867+ if (nameList.length !== 0) {
3868+ nameList.forEach(name => {
3869+ let identifiers = prompts
3870+ .filter(entry => entry.name === name)
3871+ .map(entry => entry.identifier);
3872+ identifiersList = identifiersList.concat(identifiers);
3873+ });
3874+ }
3875+
3876+ // Get the state for each prompt entry
3877+ let promptStates = new Map();
3878+ identifiersList.forEach(identifier => {
3879+ const promptOrderEntry = promptManager.getPromptOrderEntry(promptManager.activeCharacter, identifier);
3880+ if (promptOrderEntry) {
3881+ promptStates.set(identifier, promptOrderEntry.enabled);
3882+ }
3883+ });
3884+
3885+ // If return is simple (default) but more than one prompt state was retrieved, then change return type
3886+ if (returnType === 'simple' && promptStates.size > 1) {
3887+ returnType = args.identifier ? 'dict' : 'list';
3888+ }
3889+
3890+ const result = (() => {
3891+ if (returnType === 'list') return [...promptStates.values()];
3892+ if (returnType === 'dict') return Object.fromEntries(promptStates);
3893+ return [...promptStates.values()][0];
3894+ })();
3895+
3896+ return result;
3897+}
3898+
3899+/**
37903900 * Sets state of prompt entries (toggles) either via identifier/uuid or name.
37913901 * @param {object} args Object containing arguments
37923902 * @param {string} args.identifier Select prompt entry using an identifier (uuid)
@@ -3796,7 +3906,6 @@ function modelCallback(args, model) {
37963906 */
37973907function setPromptEntryCallback(args, targetState) {
37983908 // needs promptManager to manipulate prompt entries
3799- const promptManager = setupChatCompletionPromptManager(oai_settings);
38003909 const prompts = promptManager.serviceSettings.prompts;
38013910
38023911 function parseArgs(arg) {
public/scripts/st-context.js+171 -0
@@ -0,0 +1,171 @@
1+import {
2+ activateSendButtons,
3+ addOneMessage,
4+ callPopup,
5+ characters,
6+ chat,
7+ chat_metadata,
8+ create_save,
9+ deactivateSendButtons,
10+ event_types,
11+ eventSource,
12+ extension_prompts,
13+ Generate,
14+ generateQuietPrompt,
15+ getCurrentChatId,
16+ getRequestHeaders,
17+ getThumbnailUrl,
18+ main_api,
19+ max_context,
20+ menu_type,
21+ messageFormatting,
22+ name1,
23+ name2,
24+ online_status,
25+ openCharacterChat,
26+ reloadCurrentChat,
27+ renameChat,
28+ saveChatConditional,
29+ saveMetadata,
30+ saveReply,
31+ saveSettingsDebounced,
32+ selectCharacterById,
33+ sendGenerationRequest,
34+ sendStreamingRequest,
35+ sendSystemMessage,
36+ setExtensionPrompt,
37+ stopGeneration,
38+ streamingProcessor,
39+ substituteParams,
40+ substituteParamsExtended,
41+ this_chid,
42+ updateChatMetadata,
43+} from '../script.js';
44+import {
45+ extension_settings,
46+ ModuleWorkerWrapper,
47+ renderExtensionTemplate,
48+ renderExtensionTemplateAsync,
49+ writeExtensionField,
50+} from './extensions.js';
51+import { groups, openGroupChat, selected_group } from './group-chats.js';
52+import { t, translate } from './i18n.js';
53+import { hideLoader, showLoader } from './loader.js';
54+import { MacrosParser } from './macros.js';
55+import { oai_settings } from './openai.js';
56+import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
57+import { power_user, registerDebugFunction } from './power-user.js';
58+import { isMobile, shouldSendOnEnter } from './RossAscends-mods.js';
59+import { ScraperManager } from './scrapers.js';
60+import { executeSlashCommands, executeSlashCommandsWithOptions, registerSlashCommand } from './slash-commands.js';
61+import { SlashCommand } from './slash-commands/SlashCommand.js';
62+import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
63+import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
64+import { tag_map, tags } from './tags.js';
65+import { textgenerationwebui_settings } from './textgen-settings.js';
66+import { getTokenCount, getTokenCountAsync, getTokenizerModel } from './tokenizers.js';
67+import { ToolManager } from './tool-calling.js';
68+import { timestampToMoment } from './utils.js';
69+
70+export function getContext() {
71+ return {
72+ chat,
73+ characters,
74+ groups,
75+ name1,
76+ name2,
77+ characterId: this_chid,
78+ groupId: selected_group,
79+ chatId: selected_group
80+ ? groups.find(x => x.id == selected_group)?.chat_id
81+ : (characters[this_chid]?.chat),
82+ getCurrentChatId,
83+ getRequestHeaders,
84+ reloadCurrentChat,
85+ renameChat,
86+ saveSettingsDebounced,
87+ onlineStatus: online_status,
88+ maxContext: Number(max_context),
89+ chatMetadata: chat_metadata,
90+ streamingProcessor,
91+ eventSource,
92+ eventTypes: event_types,
93+ addOneMessage,
94+ generate: Generate,
95+ sendStreamingRequest,
96+ sendGenerationRequest,
97+ stopGeneration,
98+ /** @deprecated Use getTokenCountAsync instead */
99+ getTokenCount,
100+ getTokenCountAsync,
101+ extensionPrompts: extension_prompts,
102+ setExtensionPrompt,
103+ updateChatMetadata,
104+ saveChat: saveChatConditional,
105+ openCharacterChat,
106+ openGroupChat,
107+ saveMetadata,
108+ sendSystemMessage,
109+ activateSendButtons,
110+ deactivateSendButtons,
111+ saveReply,
112+ substituteParams,
113+ substituteParamsExtended,
114+ SlashCommandParser,
115+ SlashCommand,
116+ SlashCommandArgument,
117+ SlashCommandNamedArgument,
118+ ARGUMENT_TYPE,
119+ executeSlashCommandsWithOptions,
120+ /** @deprecated Use SlashCommandParser.addCommandObject() instead */
121+ registerSlashCommand,
122+ /** @deprecated Use executeSlashCommandWithOptions instead */
123+ executeSlashCommands,
124+ timestampToMoment,
125+ /** @deprecated Handlebars for extensions are no longer supported. */
126+ registerHelper: () => { },
127+ registerMacro: MacrosParser.registerMacro.bind(MacrosParser),
128+ unregisterMacro: MacrosParser.unregisterMacro.bind(MacrosParser),
129+ registerFunctionTool: ToolManager.registerFunctionTool.bind(ToolManager),
130+ unregisterFunctionTool: ToolManager.unregisterFunctionTool.bind(ToolManager),
131+ isToolCallingSupported: ToolManager.isToolCallingSupported.bind(ToolManager),
132+ canPerformToolCalls: ToolManager.canPerformToolCalls.bind(ToolManager),
133+ registerDebugFunction,
134+ /** @deprecated Use renderExtensionTemplateAsync instead. */
135+ renderExtensionTemplate,
136+ renderExtensionTemplateAsync,
137+ registerDataBankScraper: ScraperManager.registerDataBankScraper.bind(ScraperManager),
138+ /** @deprecated Use callGenericPopup or Popup instead. */
139+ callPopup,
140+ callGenericPopup,
141+ showLoader,
142+ hideLoader,
143+ mainApi: main_api,
144+ extensionSettings: extension_settings,
145+ ModuleWorkerWrapper,
146+ getTokenizerModel,
147+ generateQuietPrompt,
148+ writeExtensionField,
149+ getThumbnailUrl,
150+ selectCharacterById,
151+ messageFormatting,
152+ shouldSendOnEnter,
153+ isMobile,
154+ t,
155+ translate,
156+ tags,
157+ tagMap: tag_map,
158+ menuType: menu_type,
159+ createCharacterData: create_save,
160+ /** @deprecated Legacy snake-case naming, compatibility with old extensions */
161+ event_types: event_types,
162+ Popup,
163+ POPUP_TYPE,
164+ POPUP_RESULT,
165+ chatCompletionSettings: oai_settings,
166+ textCompletionSettings: textgenerationwebui_settings,
167+ powerUserSettings: power_user,
168+ };
169+}
170+
171+export default getContext;
public/scripts/templates/itemizationChat.html+11 -2
@@ -4,8 +4,17 @@
44 <div id="copyPromptToClipboard" class="fa-solid fa-copy menu_button" title="Copy Prompt" data-i18n="[title]Copy Prompt"></div>
55 <div id="diffPrevPrompt" class="fa-solid fa-code-compare menu_button" title="Show Prompt Differences" data-i18n="[title]Show Prompt Differences"></div>
66</h3>
7-API/Model Used: {{mainApiFriendlyName}} {{#if apiUsed}}({{apiUsed}}){{/if}} {{#if modelUsed}}&ndash; {{modelUsed}}{{/if}}<br>
7+<div>
8-Tokenizer: {{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+
918<span class="tokenItemizingSubclass">
1019 Only the white numbers really matter. All numbers are estimates.
1120 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 @@
44 <div id="copyPromptToClipboard" class="fa-solid fa-copy menu_button" title="Copy Prompt" data-i18n="[title]Copy Prompt"></div>
55 <div id="diffPrevPrompt" class="fa-solid fa-code-compare menu_button" title="Show Prompt Differences" data-i18n="[title]Show Prompt Differences"></div>
66</h3>
7-API/Model Used: {{mainApiFriendlyName}} {{#if apiUsed}}({{apiUsed}}){{/if}} {{#if modelUsed}}&ndash; {{modelUsed}}{{/if}}<br>
7+<div>
8-Tokenizer: {{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+
918<span class="tokenItemizingSubclass">
1019 Only the white numbers really matter. All numbers are estimates.
1120 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 @@
88 <li><tt>&lcub;&lcub;noop&rcub;&rcub;</tt> – <span data-i18n="help_macros_4">no operation, just an empty string.</span></li>
99 <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>
1010 <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>
1112 <li><tt>&lcub;&lcub;charPrompt&rcub;&rcub;</tt> – <span data-i18n="help_macros_7">the Character's Main Prompt override</span></li>
1213 <li><tt>&lcub;&lcub;charInstruction&rcub;&rcub;</tt> – <span data-i18n="help_macros_8">the Character's Post-History Instructions override</span></li>
1314 <li><tt>&lcub;&lcub;description&rcub;&rcub;</tt> – <span data-i18n="help_macros_9">the Character's Description</span></li>
@@ -20,7 +21,8 @@
2021 <li><tt>&lcub;&lcub;user&rcub;&rcub;</tt> – <span data-i18n="help_macros_15">your current Persona username</span></li>
2122 <li><tt>&lcub;&lcub;char&rcub;&rcub;</tt> – <span data-i18n="help_macros_16">the Character's name</span></li>
2223 <li><tt>&lcub;&lcub;char_version&rcub;&rcub;</tt> – <span data-i18n="help_macros_17">the Character's version number</span></li>
2324 <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>
2426 <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>
2527 <li><tt>&lcub;&lcub;lastMessage&rcub;&rcub;</tt> – <span data-i18n="help_macros_20">the text of the latest chat message.</span></li>
2628 <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 }
1818import { ENCODE_TOKENIZERS, TEXTGEN_TOKENIZERS, getTextTokens, tokenizers } from './tokenizers.js';
1919import { getSortableDelay, onlyUnique } from './utils.js';
2020
21-export {
22- settings as textgenerationwebui_settings,
23- loadTextGenSettings,
24- generateTextGenWithStreaming,
25- formatTextGenURL,
26-};
27-
2821export const textgen_types = {
2922 OOBA: 'ooba',
3023 MANCER: 'mancer',
@@ -197,6 +190,10 @@ const settings = {
197190 featherless_model: '',
198191};
199192
193+export {
194+ settings as textgenerationwebui_settings,
195+};
196+
200197export let textgenerationwebui_banned_in_macros = [];
201198
202199export let textgenerationwebui_presets = [];
@@ -327,7 +324,7 @@ async function selectPreset(name) {
327324 saveSettingsDebounced();
328325}
329326
330327export function formatTextGenURL(value) {
331328 try {
332329 const noFormatTypes = [MANCER, TOGETHERAI, INFERMATICAI, DREAMGEN, OPENROUTER];
333330 if (noFormatTypes.includes(settings.type)) {
@@ -465,7 +462,7 @@ function calculateLogitBias() {
465462 return result;
466463}
467464
468465export function loadTextGenSettings(data, loadedSettings) {
469466 textgenerationwebui_presets = convertPresets(data.textgenerationwebui_presets);
470467 textgenerationwebui_preset_names = data.textgenerationwebui_preset_names ?? [];
471468 Object.assign(settings, loadedSettings.textgenerationwebui_settings ?? {});
@@ -889,7 +886,7 @@ function setSettingByName(setting, value, trigger) {
889886 * @returns {Promise<(function(): AsyncGenerator<{swipes: [], text: string, toolCalls: [], logprobs: {token: string, topLogprobs: Candidate[]}|null}, void, *>)|*>}
890887 * @throws {Error} - If the response status is not OK, or from within the generator
891888 */
892889export async function generateTextGenWithStreaming(generate_data, signal) {
893890 generate_data.stream = true;
894891
895892 const response = await fetch('/api/backends/text-completions/generate', {
@@ -1268,6 +1265,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
12681265
12691266 if (settings.type === KOBOLDCPP) {
12701267 params.grammar = settings.grammar_string;
1268+ params.trim_stop = true;
12711269 }
12721270
12731271 if (settings.type === HUGGINGFACE) {
public/scripts/tokenizers.js+18 -8
@@ -8,8 +8,6 @@ import { kai_flags } from './kai-settings.js';
88import { textgen_types, textgenerationwebui_settings as textgen_settings, getTextGenServer, getTextGenModel } from './textgen-settings.js';
99import { getCurrentDreamGenModelTokenizer, getCurrentOpenRouterModelTokenizer, openRouterModels } from './textgen-models.js';
1010
11-const { OOBA, TABBY, KOBOLDCPP, VLLM, APHRODITE, LLAMACPP, OPENROUTER, DREAMGEN } = textgen_types;
12-
1311export const CHARACTERS_PER_TOKEN_RATIO = 3.35;
1412export const TOKENIZER_WARNING_KEY = 'tokenizationWarningShown';
1513export const TOKENIZER_SUPPORTED_KEY = 'tokenizationSupported';
@@ -52,8 +50,12 @@ export const ENCODE_TOKENIZERS = [
5250 //tokenizers.NERD2,
5351];
5452
55-// A list of Text Completion sources that support remote tokenization.
53+/**
56-export const TEXTGEN_TOKENIZERS = [OOBA, TABBY, KOBOLDCPP, LLAMACPP, VLLM, APHRODITE];
54+ * A list of Text Completion sources that support remote tokenization.
55+ * Populated in initTokenziers due to circular dependencies.
56+ * @type {string[]}
57+ */
58+export const TEXTGEN_TOKENIZERS = [];
5759
5860const TOKENIZER_URLS = {
5961 [tokenizers.GPT2]: {
@@ -287,7 +289,7 @@ export function getTokenizerBestMatch(forApi) {
287289 const hasTokenizerError = sessionStorage.getItem(TOKENIZER_WARNING_KEY);
288290 const hasValidEndpoint = sessionStorage.getItem(TOKENIZER_SUPPORTED_KEY);
289291 const isConnected = online_status !== 'no_connection';
290292 const isTokenizerSupported = TEXTGEN_TOKENIZERS.includes(textgen_settings.type) && (textgen_settings.type !== textgen_types.OOBA || hasValidEndpoint);
291293
292294 if (!hasTokenizerError && isConnected) {
293295 if (forApi === 'kobold' && kai_flags.can_use_tokenization) {
@@ -297,10 +299,10 @@ export function getTokenizerBestMatch(forApi) {
297299 if (forApi === 'textgenerationwebui' && isTokenizerSupported) {
298300 return tokenizers.API_TEXTGENERATIONWEBUI;
299301 }
300302 if (forApi === 'textgenerationwebui' && textgen_settings.type === textgen_types.OPENROUTER) {
301303 return getCurrentOpenRouterModelTokenizer();
302304 }
303305 if (forApi === 'textgenerationwebui' && textgen_settings.type === textgen_types.DREAMGEN) {
304306 return getCurrentDreamGenModelTokenizer();
305307 }
306308 }
@@ -576,7 +578,7 @@ export function getTokenizerModel() {
576578
577579 // And for OpenRouter (if not a site model, then it's impossible to determine the tokenizer)
578580 if (main_api == 'openai' && oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER && oai_settings.openrouter_model ||
579581 main_api == 'textgenerationwebui' && textgen_settings.type === textgen_types.OPENROUTER && textgen_settings.openrouter_model) {
580582 const model = main_api == 'openai'
581583 ? model_list.find(x => x.id === oai_settings.openrouter_model)
582584 : openRouterModels.find(x => x.id === textgen_settings.openrouter_model);
@@ -1121,6 +1123,14 @@ export function decodeTextTokens(tokenizerType, ids) {
11211123}
11221124
11231125export async function initTokenizers() {
1126+ TEXTGEN_TOKENIZERS.push(
1127+ textgen_types.OOBA,
1128+ textgen_types.TABBY,
1129+ textgen_types.KOBOLDCPP,
1130+ textgen_types.LLAMACPP,
1131+ textgen_types.VLLM,
1132+ textgen_types.APHRODITE,
1133+ );
11241134 await loadTokenCache();
11251135 registerDebugFunction('resetTokenCache', 'Reset token cache', 'Purges the calculated token counts. Use this if you want to force a full re-tokenization of all chats or suspect the token counts are wrong.', resetTokenCache);
11261136}
public/scripts/tool-calling.js+1 -1
@@ -947,7 +947,7 @@ export class ToolManager {
947947 enumProvider: toolsEnumProvider,
948948 }),
949949 ],
950950 callback: async (_, name) => {
951951 if (typeof name !== 'string' || !name) {
952952 throw new Error('The unnamed argument must be a non-empty string.');
953953 }
public/style.css+8 -2
@@ -1040,8 +1040,14 @@ body .panelControlBar {
10401040 height: var(--swipeCounterHeight);
10411041}
10421042
10431043body:not(.swipeAllMessages) .mes:not(.last_mes) .swipes-counter {
10441044 opacityvisibility: 0.3hidden;
1045+}
1046+
1047+body.swipeAllMessages .mes:not(.last_mes) .swipes-counter {
1048+ /* Avoid expensive DOM queries */
1049+ opacity: 0.3 !important;
1050+ display: flex !important;
10451051}
10461052
10471053.swipe_left {
src/endpoints/backends/chat-completions.js+1 -1
@@ -280,7 +280,7 @@ async function sendMakerSuiteRequest(request, response) {
280280 delete generationConfig.stopSequences;
281281 }
282282
283283 const should_use_system_prompt = (model.includes('gemini-1.5-flash') || model.includes('gemini-1.5-pro') || model.includes('gemini-exp-1114') || model.includesstartsWith('gemini-exp-1121')) && request.body.use_makersuite_sysprompt;
284284 const prompt = convertGooglePrompt(request.body.messages, model, should_use_system_prompt, request.body.char_name, request.body.user_name);
285285 let body = {
286286 contents: prompt.contents,
src/endpoints/characters.js+4 -3
@@ -14,7 +14,7 @@ import jimp from 'jimp';
1414
1515import { AVATAR_WIDTH, AVATAR_HEIGHT } from '../constants.js';
1616import { jsonParser, urlencodedParser } from '../express-common.js';
1717import { deepMerge, humanizedISO8601DateTime, tryParse, extractFileFromZipBuffer, MemoryLimitedMap, getConfigValue } from '../util.js';
1818import { TavernCardValidator } from '../validator/TavernCardValidator.js';
1919import { parse, write } from '../character-card-parser.js';
2020import { readWorldInfoFile } from './worldinfo.js';
@@ -23,8 +23,9 @@ import { importRisuSprites } from './sprites.js';
2323const defaultAvatarPath = './public/img/ai4.png';
2424
2525// KV-store for parsed character data
26-// 100 MB limit. Would take roughly 3000 characters to reach this limit
26+const cacheCapacity = Number(getConfigValue('cardsCacheCapacity', 100)); // MB
27-const characterDataCache = new MemoryLimitedMap(1024 * 1024 * 100);
27+// With 100 MB limit it would take roughly 3000 characters to reach this limit
28+const characterDataCache = new MemoryLimitedMap(1024 * 1024 * cacheCapacity);
2829// Some Android devices require tighter memory management
2930const isAndroid = process.platform === 'android';
3031
src/endpoints/chats.js+65 -22
@@ -191,6 +191,44 @@ function importCAIChat(userName, characterName, jsonData) {
191191}
192192
193193/**
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+ */
200+function 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+/**
194232 * Flattens `msg` and `swipes` data from Chub Chat format.
195233 * Only changes enough to make it compatible with the standard chat serialization format.
196234 * @param {string} userName User name
@@ -413,7 +451,7 @@ router.post('/import', urlencodedParser, function (request, response) {
413451 const format = request.body.file_type;
414452 const avatarUrl = (request.body.avatar_url).replace('.png', '');
415453 const characterName = request.body.character_name;
416454 const userName = request.body.user_name || 'YouUser';
417455
418456 if (!request.file) {
419457 return response.sendStatus(400);
@@ -426,33 +464,38 @@ router.post('/import', urlencodedParser, function (request, response) {
426464 if (format === 'json') {
427465 fs.unlinkSync(pathToUpload);
428466 const jsonData = JSON.parse(data);
429- if (jsonData.histories !== undefined) {
467+
430- // CAI Tools format
468+ /** @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
474+ importFunc = importCAIChat;
475+ } else if (Array.isArray(jsonData.data_visible)) { // oobabooga's format
476+ importFunc = importOobaChat;
477+ } else if (Array.isArray(jsonData.messages)) { // Agnai's format
478+ importFunc = importAgnaiChat;
479+ } else { // Unknown format
480+ console.log('Incorrect chat format .json');
481+ return response.send({ error: true });
436482 }
437- return response.send({ res: true });
483+
438- } else if (Array.isArray(jsonData.data_visible)) {
484+ const handleChat = (chat) => {
439- // oobabooga's format
440- const chat = importOobaChat(userName, characterName, jsonData);
441- const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`;
442- const filePath = path.join(request.user.directories.chats, avatarUrl, fileName);
443- writeFileAtomicSync(filePath, chat, 'utf8');
444- return response.send({ res: true });
445- } else if (Array.isArray(jsonData.messages)) {
446- // Agnai format
447- const chat = importAgnaiChat(userName, characterName, jsonData);
448485 const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`;
449486 const filePath = path.join(request.user.directories.chats, avatarUrl, fileName);
450487 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);
452494 } else {
453- console.log('Incorrect chat format .json');
495+ handleChat(chat);
454- return response.send({ error: true });
455496 }
497+
498+ return response.send({ res: true });
456499 }
457500
458501 if (format === 'jsonl') {
src/endpoints/users-public.js+8 -8
@@ -67,17 +67,17 @@ router.post('/login', jsonParser, async (request, response) => {
6767 const user = await storage.getItem(toKey(request.body.handle));
6868
6969 if (!user) {
7070 console.log('Login failed: User', request.body.handle, 'not found');
7171 return response.status(403).json({ error: 'Incorrect credentials' });
7272 }
7373
7474 if (!user.enabled) {
7575 console.log('Login failed: User', user.handle, 'is disabled');
7676 return response.status(403).json({ error: 'User is disabled' });
7777 }
7878
7979 if (user.password && user.password !== getPasswordHash(request.body.password, user.salt)) {
8080 console.log('Login failed: Incorrect password for', user.handle);
8181 return response.status(403).json({ error: 'Incorrect credentials' });
8282 }
8383
@@ -88,7 +88,7 @@ router.post('/login', jsonParser, async (request, response) => {
8888
8989 await loginLimiter.delete(ip);
9090 request.session.handle = user.handle;
9191 console.log('Login successful:', user.handle, request'from', ip, 'at', new Date().sessiontoLocaleString());
9292 return response.json({ handle: user.handle });
9393 } catch (error) {
9494 if (error instanceof RateLimiterRes) {
@@ -115,12 +115,12 @@ router.post('/recover-step1', jsonParser, async (request, response) => {
115115 const user = await storage.getItem(toKey(request.body.handle));
116116
117117 if (!user) {
118118 console.log('Recover step 1 failed: User', request.body.handle, 'not found');
119119 return response.status(404).json({ error: 'User not found' });
120120 }
121121
122122 if (!user.enabled) {
123123 console.log('Recover step 1 failed: User', user.handle, 'is disabled');
124124 return response.status(403).json({ error: 'User is disabled' });
125125 }
126126
@@ -153,12 +153,12 @@ router.post('/recover-step2', jsonParser, async (request, response) => {
153153 const ip = getIpFromRequest(request);
154154
155155 if (!user) {
156156 console.log('Recover step 2 failed: User', request.body.handle, 'not found');
157157 return response.status(404).json({ error: 'User not found' });
158158 }
159159
160160 if (!user.enabled) {
161161 console.log('Recover step 2 failed: User', user.handle, 'is disabled');
162162 return response.status(403).json({ error: 'User is disabled' });
163163 }
164164
src/prompt-converters.js+1 -0
@@ -347,6 +347,7 @@ export function convertGooglePrompt(messages, model, useSysPrompt = false, charN
347347 'gemini-1.5-flash-8b-exp-0924',
348348 'gemini-exp-1114',
349349 'gemini-exp-1121',
350+ 'gemini-exp-1206',
350351 'gemini-1.5-pro',
351352 'gemini-1.5-pro-latest',
352353 'gemini-1.5-pro-001',
src/util.js+3 -2
@@ -680,8 +680,9 @@ export class MemoryLimitedMap {
680680 * @param {number} maxMemoryInBytes - The maximum allowed memory in bytes for string values.
681681 */
682682 constructor(maxMemoryInBytes) {
683683 if (typeof maxMemoryInBytes !== 'number' || maxMemoryInBytes <= 0 || isNaN(maxMemoryInBytes)) {
684- throw new Error('maxMemoryInBytes must be a positive number');
684+ console.warn('Invalid maxMemoryInBytes, using a fallback value of 1 GB.');
685+ maxMemoryInBytes = 1024 * 1024 * 1024; // 1 GB
685686 }
686687 this.maxMemory = maxMemoryInBytes;
687688 this.currentMemory = 0;