Merge branch 'staging' into persona-lorebook

This commit is contained in:
Cohee
2024-12-07 15:52:23 +02:00
30 changed files with 438 additions and 236 deletions

View File

@ -54,6 +54,7 @@ module.exports = {
}, },
// These scripts are loaded in HTML; tell ESLint not to complain about them being undefined // These scripts are loaded in HTML; tell ESLint not to complain about them being undefined
globals: { globals: {
globalThis: 'readonly',
ePub: 'readonly', ePub: 'readonly',
pdfjsLib: 'readonly', pdfjsLib: 'readonly',
toastr: 'readonly', toastr: 'readonly',

View File

@ -1,6 +1,8 @@
# -- DATA CONFIGURATION -- # -- DATA CONFIGURATION --
# Root directory for user data storage # Root directory for user data storage
dataRoot: ./data dataRoot: ./data
# The maximum amount of memory that parsed character cards can use in MB
cardsCacheCapacity: 100
# -- SERVER CONFIGURATION -- # -- SERVER CONFIGURATION --
# Listen for incoming connections # Listen for incoming connections
listen: false listen: false

22
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "sillytavern", "name": "sillytavern",
"version": "1.12.8", "version": "1.12.9",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "sillytavern", "name": "sillytavern",
"version": "1.12.8", "version": "1.12.9",
"hasInstallScript": true, "hasInstallScript": true,
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
@ -3591,9 +3591,9 @@
"integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==" "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="
}, },
"node_modules/express": { "node_modules/express": {
"version": "4.21.1", "version": "4.21.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"accepts": "~1.3.8", "accepts": "~1.3.8",
@ -3615,7 +3615,7 @@
"methods": "~1.1.2", "methods": "~1.1.2",
"on-finished": "2.4.1", "on-finished": "2.4.1",
"parseurl": "~1.3.3", "parseurl": "~1.3.3",
"path-to-regexp": "0.1.10", "path-to-regexp": "0.1.12",
"proxy-addr": "~2.0.7", "proxy-addr": "~2.0.7",
"qs": "6.13.0", "qs": "6.13.0",
"range-parser": "~1.2.1", "range-parser": "~1.2.1",
@ -3630,6 +3630,10 @@
}, },
"engines": { "engines": {
"node": ">= 0.10.0" "node": ">= 0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
} }
}, },
"node_modules/express/node_modules/cookie": { "node_modules/express/node_modules/cookie": {
@ -5725,9 +5729,9 @@
} }
}, },
"node_modules/path-to-regexp": { "node_modules/path-to-regexp": {
"version": "0.1.10", "version": "0.1.12",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/peek-readable": { "node_modules/peek-readable": {

View File

@ -84,7 +84,7 @@
"type": "git", "type": "git",
"url": "https://github.com/SillyTavern/SillyTavern.git" "url": "https://github.com/SillyTavern/SillyTavern.git"
}, },
"version": "1.12.8", "version": "1.12.9",
"scripts": { "scripts": {
"start": "node server.js", "start": "node server.js",
"start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js", "start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js",

3
public/global.d.ts vendored
View File

@ -1,4 +1,5 @@
import libs from './lib'; import libs from './lib';
import getContext from './scripts/st-context';
// Global namespace modules // Global namespace modules
declare var ai; declare var ai;
@ -6,7 +7,7 @@ declare var pdfjsLib;
declare var ePub; declare var ePub;
declare var SillyTavern: { declare var SillyTavern: {
getContext(): any; getContext(): typeof getContext;
llm: any; llm: any;
libs: typeof libs; libs: typeof libs;
}; };

View File

@ -2987,6 +2987,7 @@
<optgroup label="Subversions"> <optgroup label="Subversions">
<option value="gemini-exp-1121">Gemini Experimental 2024-11-21</option> <option value="gemini-exp-1121">Gemini Experimental 2024-11-21</option>
<option value="gemini-exp-1114">Gemini Experimental 2024-11-14</option> <option value="gemini-exp-1114">Gemini Experimental 2024-11-14</option>
<option value="gemini-exp-1206">Gemini Experimental 2024-12-06</option>
<option value="gemini-1.5-pro-exp-0801">Gemini 1.5 Pro Experiment 2024-08-01</option> <option value="gemini-1.5-pro-exp-0801">Gemini 1.5 Pro Experiment 2024-08-01</option>
<option value="gemini-1.5-pro-exp-0827">Gemini 1.5 Pro Experiment 2024-08-27</option> <option value="gemini-1.5-pro-exp-0827">Gemini 1.5 Pro Experiment 2024-08-27</option>
<option value="gemini-1.5-pro-latest">Gemini 1.5 Pro [latest]</option> <option value="gemini-1.5-pro-latest">Gemini 1.5 Pro [latest]</option>
@ -4183,7 +4184,7 @@
</label> </label>
<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."> <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.">
<input id="show_swipe_num_all_messages" type="checkbox" /> <input id="show_swipe_num_all_messages" type="checkbox" />
<small data-i18n="Swipe # for All Messages">Swipe # for All Messages</small><i class="fa-solid fa-mobile-screen-button"></i> <small data-i18n="Swipe # for All Messages">Swipe # for All Messages</small>
</label> </label>
<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"> <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">
<input id="hotswapEnabled" type="checkbox" /> <input id="hotswapEnabled" type="checkbox" />

View File

@ -5,7 +5,8 @@
"module": "ESNext", "module": "ESNext",
"moduleResolution": "node", "moduleResolution": "node",
"allowUmdGlobalAccess": true, "allowUmdGlobalAccess": true,
"allowSyntheticDefaultImports": true "allowSyntheticDefaultImports": true,
"strictBindCallApply": true
}, },
"exclude": [ "exclude": [
"**/node_modules/**", "**/node_modules/**",

View File

@ -13,7 +13,7 @@ import {
default as libs, default as libs,
} from './lib.js'; } from './lib.js';
import { humanizedDateTime, favsToHotswap, getMessageTimeStamp, dragElement, isMobile, initRossMods, shouldSendOnEnter } from './scripts/RossAscends-mods.js'; import { humanizedDateTime, favsToHotswap, getMessageTimeStamp, dragElement, isMobile, initRossMods } from './scripts/RossAscends-mods.js';
import { userStatsHandler, statMesProcess, initStats } from './scripts/stats.js'; import { userStatsHandler, statMesProcess, initStats } from './scripts/stats.js';
import { import {
generateKoboldWithStreaming, generateKoboldWithStreaming,
@ -37,8 +37,6 @@ import {
parseTabbyLogprobs, parseTabbyLogprobs,
} from './scripts/textgen-settings.js'; } from './scripts/textgen-settings.js';
const { MANCER, TOGETHERAI, OOBA, VLLM, APHRODITE, TABBY, OLLAMA, INFERMATICAI, DREAMGEN, OPENROUTER, FEATHERLESS } = textgen_types;
import { import {
world_info, world_info,
getWorldInfoPrompt, getWorldInfoPrompt,
@ -67,9 +65,7 @@ import {
getGroupChat, getGroupChat,
renameGroupMember, renameGroupMember,
createNewGroupChat, createNewGroupChat,
getGroupPastChats,
getGroupAvatar, getGroupAvatar,
openGroupChat,
editGroup, editGroup,
deleteGroupChat, deleteGroupChat,
renameGroupChat, renameGroupChat,
@ -98,7 +94,6 @@ import {
resetMovableStyles, resetMovableStyles,
forceCharacterEditorTokenize, forceCharacterEditorTokenize,
applyPowerUserSettings, applyPowerUserSettings,
switchSwipeNumAllMessages,
} from './scripts/power-user.js'; } from './scripts/power-user.js';
import { import {
@ -174,8 +169,8 @@ import {
} from './scripts/utils.js'; } from './scripts/utils.js';
import { debounce_timeout } from './scripts/constants.js'; import { debounce_timeout } from './scripts/constants.js';
import { ModuleWorkerWrapper, doDailyExtensionUpdatesCheck, extension_settings, getContext, initExtensions, loadExtensionSettings, renderExtensionTemplate, renderExtensionTemplateAsync, runGenerationInterceptors, saveMetadataDebounced, writeExtensionField } from './scripts/extensions.js'; import { doDailyExtensionUpdatesCheck, extension_settings, initExtensions, loadExtensionSettings, runGenerationInterceptors, saveMetadataDebounced } from './scripts/extensions.js';
import { COMMENT_NAME_DEFAULT, executeSlashCommands, executeSlashCommandsOnChatInput, executeSlashCommandsWithOptions, getSlashCommandsHelp, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, processChatSlashCommands, registerSlashCommand, stopScriptExecution } from './scripts/slash-commands.js'; import { COMMENT_NAME_DEFAULT, executeSlashCommandsOnChatInput, getSlashCommandsHelp, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, processChatSlashCommands, stopScriptExecution } from './scripts/slash-commands.js';
import { import {
tag_map, tag_map,
tags, tags,
@ -225,8 +220,8 @@ import {
instruct_presets, instruct_presets,
selectContextPreset, selectContextPreset,
} from './scripts/instruct-mode.js'; } from './scripts/instruct-mode.js';
import { initLocales, t, translate } from './scripts/i18n.js'; import { initLocales, t } from './scripts/i18n.js';
import { getFriendlyTokenizerName, getTokenCount, getTokenCountAsync, getTokenizerModel, initTokenizers, saveTokenCache, TOKENIZER_SUPPORTED_KEY } from './scripts/tokenizers.js'; import { getFriendlyTokenizerName, getTokenCount, getTokenCountAsync, initTokenizers, saveTokenCache, TOKENIZER_SUPPORTED_KEY } from './scripts/tokenizers.js';
import { import {
user_avatar, user_avatar,
getUserAvatars, getUserAvatars,
@ -242,11 +237,11 @@ import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay
import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels, loadTabbyModels } from './scripts/textgen-models.js'; import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels, loadTabbyModels } from './scripts/textgen-models.js';
import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId, preserveNeutralChat, restoreNeutralChat } from './scripts/chats.js'; import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId, preserveNeutralChat, restoreNeutralChat } from './scripts/chats.js';
import { getPresetManager, initPresetManager } from './scripts/preset-manager.js'; import { getPresetManager, initPresetManager } from './scripts/preset-manager.js';
import { MacrosParser, evaluateMacros, getLastMessageId, initMacros } from './scripts/macros.js'; import { evaluateMacros, getLastMessageId, initMacros } from './scripts/macros.js';
import { currentUser, setUserControls } from './scripts/user.js'; import { currentUser, setUserControls } from './scripts/user.js';
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js'; import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js';
import { renderTemplate, renderTemplateAsync } from './scripts/templates.js'; import { renderTemplate, renderTemplateAsync } from './scripts/templates.js';
import { initScrapers, ScraperManager } from './scripts/scrapers.js'; import { initScrapers } from './scripts/scrapers.js';
import { SlashCommandParser } from './scripts/slash-commands/SlashCommandParser.js'; import { SlashCommandParser } from './scripts/slash-commands/SlashCommandParser.js';
import { SlashCommand } from './scripts/slash-commands/SlashCommand.js'; import { SlashCommand } from './scripts/slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './scripts/slash-commands/SlashCommandArgument.js'; import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './scripts/slash-commands/SlashCommandArgument.js';
@ -268,6 +263,13 @@ import { initServerHistory } from './scripts/server-history.js';
import { initSettingsSearch } from './scripts/setting-search.js'; import { initSettingsSearch } from './scripts/setting-search.js';
import { initBulkEdit } from './scripts/bulk-edit.js'; import { initBulkEdit } from './scripts/bulk-edit.js';
import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js'; import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js';
import { getContext } from './scripts/st-context.js';
// API OBJECT FOR EXTERNAL WIRING
globalThis.SillyTavern = {
libs,
getContext,
};
//exporting functions and vars for mods //exporting functions and vars for mods
export { export {
@ -427,10 +429,6 @@ DOMPurify.addHook('uponSanitizeElement', (node, _, config) => {
} }
}); });
// API OBJECT FOR EXTERNAL WIRING
window['SillyTavern'] = {};
window['SillyTavern'].libs = libs;
// Event source init // Event source init
export const event_types = { export const event_types = {
APP_READY: 'app_ready', APP_READY: 'app_ready',
@ -536,7 +534,7 @@ let displayVersion = 'SillyTavern';
let generatedPromptCache = ''; let generatedPromptCache = '';
let generation_started = new Date(); let generation_started = new Date();
/** @type {import('scripts/char-data.js').v1CharData[]} */ /** @type {import('./scripts/char-data.js').v1CharData[]} */
export let characters = []; export let characters = [];
export let this_chid; export let this_chid;
let saveCharactersPage = 0; let saveCharactersPage = 0;
@ -812,7 +810,7 @@ export let menu_type = '';
export let selected_button = ''; //which button pressed export let selected_button = ''; //which button pressed
//create pole save //create pole save
let create_save = { export let create_save = {
name: '', name: '',
description: '', description: '',
creator_notes: '', creator_notes: '',
@ -864,7 +862,7 @@ export let amount_gen = 80; //default max length of AI generated responses
export let max_context = 2048; export let max_context = 2048;
var swipes = true; var swipes = true;
let extension_prompts = {}; export let extension_prompts = {};
export let main_api;// = "kobold"; export let main_api;// = "kobold";
//novel settings //novel settings
@ -1175,7 +1173,7 @@ async function getStatusTextgen() {
return resultCheckStatus(); return resultCheckStatus();
} }
if (textgen_settings.type == OOBA && textgen_settings.bypass_status_check) { if (textgen_settings.type == textgen_types.OOBA && textgen_settings.bypass_status_check) {
setOnlineStatus('Status check bypassed'); setOnlineStatus('Status check bypassed');
return resultCheckStatus(); return resultCheckStatus();
} }
@ -1193,34 +1191,34 @@ async function getStatusTextgen() {
const data = await response.json(); const data = await response.json();
if (textgen_settings.type === MANCER) { if (textgen_settings.type === textgen_types.MANCER) {
loadMancerModels(data?.data); loadMancerModels(data?.data);
setOnlineStatus(textgen_settings.mancer_model); setOnlineStatus(textgen_settings.mancer_model);
} else if (textgen_settings.type === TOGETHERAI) { } else if (textgen_settings.type === textgen_types.TOGETHERAI) {
loadTogetherAIModels(data?.data); loadTogetherAIModels(data?.data);
setOnlineStatus(textgen_settings.togetherai_model); setOnlineStatus(textgen_settings.togetherai_model);
} else if (textgen_settings.type === OLLAMA) { } else if (textgen_settings.type === textgen_types.OLLAMA) {
loadOllamaModels(data?.data); loadOllamaModels(data?.data);
setOnlineStatus(textgen_settings.ollama_model || 'Connected'); setOnlineStatus(textgen_settings.ollama_model || 'Connected');
} else if (textgen_settings.type === INFERMATICAI) { } else if (textgen_settings.type === textgen_types.INFERMATICAI) {
loadInfermaticAIModels(data?.data); loadInfermaticAIModels(data?.data);
setOnlineStatus(textgen_settings.infermaticai_model); setOnlineStatus(textgen_settings.infermaticai_model);
} else if (textgen_settings.type === DREAMGEN) { } else if (textgen_settings.type === textgen_types.DREAMGEN) {
loadDreamGenModels(data?.data); loadDreamGenModels(data?.data);
setOnlineStatus(textgen_settings.dreamgen_model); setOnlineStatus(textgen_settings.dreamgen_model);
} else if (textgen_settings.type === OPENROUTER) { } else if (textgen_settings.type === textgen_types.OPENROUTER) {
loadOpenRouterModels(data?.data); loadOpenRouterModels(data?.data);
setOnlineStatus(textgen_settings.openrouter_model); setOnlineStatus(textgen_settings.openrouter_model);
} else if (textgen_settings.type === VLLM) { } else if (textgen_settings.type === textgen_types.VLLM) {
loadVllmModels(data?.data); loadVllmModels(data?.data);
setOnlineStatus(textgen_settings.vllm_model); setOnlineStatus(textgen_settings.vllm_model);
} else if (textgen_settings.type === APHRODITE) { } else if (textgen_settings.type === textgen_types.APHRODITE) {
loadAphroditeModels(data?.data); loadAphroditeModels(data?.data);
setOnlineStatus(textgen_settings.aphrodite_model); setOnlineStatus(textgen_settings.aphrodite_model);
} else if (textgen_settings.type === FEATHERLESS) { } else if (textgen_settings.type === textgen_types.FEATHERLESS) {
loadFeatherlessModels(data?.data); loadFeatherlessModels(data?.data);
setOnlineStatus(textgen_settings.featherless_model); setOnlineStatus(textgen_settings.featherless_model);
} else if (textgen_settings.type === TABBY) { } else if (textgen_settings.type === textgen_types.TABBY) {
loadTabbyModels(data?.data); loadTabbyModels(data?.data);
setOnlineStatus(textgen_settings.tabby_model || data?.result); setOnlineStatus(textgen_settings.tabby_model || data?.result);
} else { } else {
@ -1637,7 +1635,7 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) {
subEntities = entitiesFilter.applyFilters(subEntities, { clearScoreCache: false, tempOverrides: { [FILTER_TYPES.FOLDER]: FILTER_STATES.UNDEFINED }, clearFuzzySearchCaches: false }); subEntities = entitiesFilter.applyFilters(subEntities, { clearScoreCache: false, tempOverrides: { [FILTER_TYPES.FOLDER]: FILTER_STATES.UNDEFINED }, clearFuzzySearchCaches: false });
} }
if (doSort) { if (doSort) {
sortEntitiesList(subEntities); sortEntitiesList(subEntities, false);
} }
entity.entities = subEntities; entity.entities = subEntities;
entity.hidden = subCount - subEntities.length; entity.hidden = subCount - subEntities.length;
@ -1665,7 +1663,7 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) {
// Sort before returning if requested // Sort before returning if requested
if (doSort) { if (doSort) {
sortEntitiesList(entities); sortEntitiesList(entities, false);
} }
entitiesFilter.clearFuzzySearchCaches(); entitiesFilter.clearFuzzySearchCaches();
return entities; return entities;
@ -2607,15 +2605,18 @@ export function substituteParams(content, _name1, _name2, _original, _group, _re
}; };
} }
const getGroupValue = () => { const getGroupValue = (includeMuted) => {
if (typeof _group === 'string') { if (typeof _group === 'string') {
return _group; return _group;
} }
if (selected_group) { if (selected_group) {
const members = groups.find(x => x.id === selected_group)?.members; const members = groups.find(x => x.id === selected_group)?.members;
/** @type {string[]} */
const disabledMembers = groups.find(x => x.id === selected_group)?.disabled_members ?? [];
const isMuted = x => includeMuted ? true : !disabledMembers.includes(x);
const names = Array.isArray(members) const names = Array.isArray(members)
? members.map(m => characters.find(c => c.avatar === m)?.name).filter(Boolean).join(', ') ? members.filter(isMuted).map(m => characters.find(c => c.avatar === m)?.name).filter(Boolean).join(', ')
: ''; : '';
return names; return names;
} else { } else {
@ -2639,7 +2640,8 @@ export function substituteParams(content, _name1, _name2, _original, _group, _re
// Must be substituted last so that they're replaced inside {{description}} // Must be substituted last so that they're replaced inside {{description}}
environment.user = _name1 ?? name1; environment.user = _name1 ?? name1;
environment.char = _name2 ?? name2; environment.char = _name2 ?? name2;
environment.group = environment.charIfNotGroup = getGroupValue(); environment.group = environment.charIfNotGroup = getGroupValue(true);
environment.groupNotMuted = getGroupValue(false);
environment.model = getGeneratingModel(); environment.model = getGeneratingModel();
if (additionalMacro && typeof additionalMacro === 'object') { if (additionalMacro && typeof additionalMacro === 'object') {
@ -5538,7 +5540,7 @@ function extractMultiSwipes(data, type) {
return swipes; return swipes;
} }
if (main_api === 'openai' || (main_api === 'textgenerationwebui' && [MANCER, VLLM, APHRODITE, TABBY, INFERMATICAI].includes(textgen_settings.type))) { 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))) {
if (!Array.isArray(data.choices)) { if (!Array.isArray(data.choices)) {
return swipes; return swipes;
} }
@ -5701,7 +5703,7 @@ export function cleanUpMessage(getMessage, isImpersonate, isContinue, displayInc
return getMessage; return getMessage;
} }
async function saveReply(type, getMessage, fromStreaming, title, swipes) { export async function saveReply(type, getMessage, fromStreaming, title, swipes) {
if (type != 'append' && type != 'continue' && type != 'appendFinal' && chat.length && (chat[chat.length - 1]['swipe_id'] === undefined || if (type != 'append' && type != 'continue' && type != 'appendFinal' && chat.length && (chat[chat.length - 1]['swipe_id'] === undefined ||
chat[chat.length - 1]['is_user'])) { chat[chat.length - 1]['is_user'])) {
type = 'normal'; type = 'normal';
@ -7660,14 +7662,12 @@ export function showSwipeButtons() {
//allows for writing individual swipe counters for past messages //allows for writing individual swipe counters for past messages
const lastSwipeCounter = $('.last_mes .swipes-counter'); const lastSwipeCounter = $('.last_mes .swipes-counter');
lastSwipeCounter.text(swipeCounterText).show(); lastSwipeCounter.text(swipeCounterText).show();
switchSwipeNumAllMessages();
} }
export function hideSwipeButtons() { export function hideSwipeButtons() {
$('#chat').find('.swipe_right').hide(); chatElement.find('.swipe_right').hide();
$('#chat').find('.last_mes .swipes-counter').hide(); chatElement.find('.last_mes .swipes-counter').hide();
$('#chat').find('.swipe_left').hide(); chatElement.find('.swipe_left').hide();
} }
/** /**
@ -8233,102 +8233,6 @@ async function createOrEditCharacter(e) {
} }
} }
window['SillyTavern'].getContext = function () {
return {
chat: chat,
characters: characters,
groups: groups,
name1: name1,
name2: name2,
characterId: this_chid,
groupId: selected_group,
chatId: selected_group
? groups.find(x => x.id == selected_group)?.chat_id
: (this_chid && characters[this_chid] && characters[this_chid].chat),
getCurrentChatId: getCurrentChatId,
getRequestHeaders: getRequestHeaders,
reloadCurrentChat: reloadCurrentChat,
renameChat: renameChat,
saveSettingsDebounced: saveSettingsDebounced,
onlineStatus: online_status,
maxContext: Number(max_context),
chatMetadata: chat_metadata,
streamingProcessor,
eventSource: eventSource,
eventTypes: event_types,
addOneMessage: addOneMessage,
generate: Generate,
sendStreamingRequest: sendStreamingRequest,
sendGenerationRequest: sendGenerationRequest,
stopGeneration: stopGeneration,
getTokenCount: getTokenCount,
extensionPrompts: extension_prompts,
setExtensionPrompt: setExtensionPrompt,
updateChatMetadata: updateChatMetadata,
saveChat: saveChatConditional,
openCharacterChat: openCharacterChat,
openGroupChat: openGroupChat,
saveMetadata: saveMetadata,
sendSystemMessage: sendSystemMessage,
activateSendButtons,
deactivateSendButtons,
saveReply,
substituteParams,
substituteParamsExtended,
SlashCommandParser,
SlashCommand,
SlashCommandArgument,
SlashCommandNamedArgument,
ARGUMENT_TYPE,
executeSlashCommandsWithOptions,
/** @deprecated Use SlashCommandParser.addCommandObject() instead */
registerSlashCommand: registerSlashCommand,
/** @deprecated Use executeSlashCommandWithOptions instead */
executeSlashCommands: executeSlashCommands,
timestampToMoment: timestampToMoment,
/** @deprecated Handlebars for extensions are no longer supported. */
registerHelper: () => { },
registerMacro: MacrosParser.registerMacro.bind(MacrosParser),
unregisterMacro: MacrosParser.unregisterMacro.bind(MacrosParser),
registerFunctionTool: ToolManager.registerFunctionTool.bind(ToolManager),
unregisterFunctionTool: ToolManager.unregisterFunctionTool.bind(ToolManager),
isToolCallingSupported: ToolManager.isToolCallingSupported.bind(ToolManager),
canPerformToolCalls: ToolManager.canPerformToolCalls.bind(ToolManager),
registerDebugFunction: registerDebugFunction,
/** @deprecated Use renderExtensionTemplateAsync instead. */
renderExtensionTemplate: renderExtensionTemplate,
renderExtensionTemplateAsync: renderExtensionTemplateAsync,
registerDataBankScraper: ScraperManager.registerDataBankScraper,
/** @deprecated Use callGenericPopup or Popup instead. */
callPopup: callPopup,
callGenericPopup: callGenericPopup,
showLoader: showLoader,
hideLoader: hideLoader,
mainApi: main_api,
extensionSettings: extension_settings,
ModuleWorkerWrapper: ModuleWorkerWrapper,
getTokenizerModel: getTokenizerModel,
generateQuietPrompt: generateQuietPrompt,
writeExtensionField: writeExtensionField,
getThumbnailUrl: getThumbnailUrl,
selectCharacterById: selectCharacterById,
messageFormatting: messageFormatting,
shouldSendOnEnter: shouldSendOnEnter,
isMobile: isMobile,
t: t,
translate: translate,
tags: tags,
tagMap: tag_map,
menuType: menu_type,
createCharacterData: create_save,
/** @deprecated Legacy snake-case naming, compatibility with old extensions */
event_types: event_types,
Popup: Popup,
POPUP_TYPE: POPUP_TYPE,
POPUP_RESULT: POPUP_RESULT,
};
};
/** /**
* Formats a counter for a swipe view. * Formats a counter for a swipe view.
* @param {number} current The current number of items. * @param {number} current The current number of items.

View File

@ -75,6 +75,7 @@
* @property {string} [source_url] - The source URL associated with the character. * @property {string} [source_url] - The source URL associated with the character.
* @property {{full_path: string}} [chub] - The Chub-specific data associated with the character. * @property {{full_path: string}} [chub] - The Chub-specific data associated with the character.
* @property {{source: string[]}} [risuai] - The RisuAI-specific data associated with the character. * @property {{source: string[]}} [risuai] - The RisuAI-specific data associated with the character.
* @property {{positive: string, negative: string}} [sd_character_prompt] - SD-specific data associated with the character.
*/ */
/** /**

View File

@ -5,6 +5,7 @@ import { showLoader } from './loader.js';
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
import { renderTemplate, renderTemplateAsync } from './templates.js'; import { renderTemplate, renderTemplateAsync } from './templates.js';
import { isSubsetOf, setValueByPath } from './utils.js'; import { isSubsetOf, setValueByPath } from './utils.js';
import { getContext } from './st-context.js';
export { export {
getContext, getContext,
getApiUrl, getApiUrl,
@ -174,7 +175,6 @@ const extension_settings = {
let modules = []; let modules = [];
let activeExtensions = new Set(); let activeExtensions = new Set();
const getContext = () => window['SillyTavern'].getContext();
const getApiUrl = () => extension_settings.apiUrl; const getApiUrl = () => extension_settings.apiUrl;
let connectedToApi = false; let connectedToApi = false;

View File

@ -62,6 +62,7 @@
<option data-type="google" value="gemini-1.5-flash-8b-exp-0924">gemini-1.5-flash-8b-exp-0924</option> <option data-type="google" value="gemini-1.5-flash-8b-exp-0924">gemini-1.5-flash-8b-exp-0924</option>
<option data-type="google" value="gemini-exp-1114">gemini-exp-1114</option> <option data-type="google" value="gemini-exp-1114">gemini-exp-1114</option>
<option data-type="google" value="gemini-exp-1121">gemini-exp-1121</option> <option data-type="google" value="gemini-exp-1121">gemini-exp-1121</option>
<option data-type="google" value="gemini-exp-1206">gemini-exp-1206</option>
<option data-type="google" value="gemini-1.5-pro">gemini-1.5-pro</option> <option data-type="google" value="gemini-1.5-pro">gemini-1.5-pro</option>
<option data-type="google" value="gemini-1.5-pro-latest">gemini-1.5-pro-latest</option> <option data-type="google" value="gemini-1.5-pro-latest">gemini-1.5-pro-latest</option>
<option data-type="google" value="gemini-1.5-pro-001">gemini-1.5-pro-001</option> <option data-type="google" value="gemini-1.5-pro-001">gemini-1.5-pro-001</option>

View File

@ -60,7 +60,6 @@ import { ToolManager } from '../../tool-calling.js';
export { MODULE_NAME }; export { MODULE_NAME };
const MODULE_NAME = 'sd'; const MODULE_NAME = 'sd';
const UPDATE_INTERVAL = 1000;
// This is a 1x1 transparent PNG // This is a 1x1 transparent PNG
const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='; const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
const CUSTOM_STOP_EVENT = 'sd_stop_generation'; const CUSTOM_STOP_EVENT = 'sd_stop_generation';
@ -3687,8 +3686,6 @@ async function addSDGenButtons() {
const button = $('#sd_gen'); const button = $('#sd_gen');
const dropdown = $('#sd_dropdown'); const dropdown = $('#sd_dropdown');
dropdown.hide(); dropdown.hide();
button.hide();
messageButton.hide();
let popper = Popper.createPopper(button.get(0), dropdown.get(0), { let popper = Popper.createPopper(button.get(0), dropdown.get(0), {
placement: 'top', placement: 'top',
@ -3770,18 +3767,6 @@ function isValidState() {
} }
} }
async function moduleWorker() {
if (isValidState()) {
$('#sd_gen').show();
$('.sd_message_gen').show();
}
else {
$('#sd_gen').hide();
$('.sd_message_gen').hide();
}
}
setInterval(moduleWorker, UPDATE_INTERVAL);
let buttonAbortController = null; let buttonAbortController = null;
async function sdMessageButton(e) { async function sdMessageButton(e) {

View File

@ -4,7 +4,7 @@
#sd_dropdown { #sd_dropdown {
z-index: 30000; z-index: 30000;
backdrop-filter: blur(--SmartThemeBlurStrength); backdrop-filter: blur(var(--SmartThemeBlurStrength));
} }
#sd_comfy_open_workflow_editor { #sd_comfy_open_workflow_editor {

View File

@ -93,7 +93,7 @@
.at-settings-separator { .at-settings-separator {
margin-top: 10px; margin-top: 10px;
margin-bottom: 10px; margin-bottom: 10px;
padding: 18x; padding: 18px;
font-weight: bold; font-weight: bold;
border-top: 1px solid #e1e1e1; /* Grey line */ border-top: 1px solid #e1e1e1; /* Grey line */
border-bottom: 1px solid #e1e1e1; /* Grey line */ border-bottom: 1px solid #e1e1e1; /* Grey line */

View File

@ -1265,18 +1265,20 @@ function getGroupCharacters({ doFilter, onlyMembers } = {}) {
const thisGroup = openGroupId && groups.find((x) => x.id == openGroupId); const thisGroup = openGroupId && groups.find((x) => x.id == openGroupId);
let candidates = characters let candidates = characters
.filter((x) => isGroupMember(thisGroup, x.avatar) == onlyMembers) .filter((x) => isGroupMember(thisGroup, x.avatar) == onlyMembers)
.map((x, index) => ({ item: x, id: index, type: 'character' })); .map((x) => ({ item: x, id: characters.indexOf(x), type: 'character' }));
if (onlyMembers) {
candidates.sort(sortMembersFn);
} else {
sortEntitiesList(candidates);
}
if (doFilter) { if (doFilter) {
candidates = groupCandidatesFilter.applyFilters(candidates); candidates = groupCandidatesFilter.applyFilters(candidates);
} }
if (onlyMembers) {
candidates.sort(sortMembersFn);
} else {
const useFilterOrder = doFilter && !!$('#rm_group_filter').val();
sortEntitiesList(candidates, useFilterOrder, groupCandidatesFilter);
}
groupCandidatesFilter.clearFuzzySearchCaches();
return candidates; return candidates;
} }

View File

@ -379,8 +379,8 @@ export let selected_proxy = proxies[0];
let openai_setting_names; let openai_setting_names;
let openai_settings; let openai_settings;
/** @type {import('./PromptManager.js').PromptManager} */
let promptManager = null; export let promptManager = null;
async function validateReverseProxy() { async function validateReverseProxy() {
if (!oai_settings.reverse_proxy) { if (!oai_settings.reverse_proxy) {
@ -4077,7 +4077,7 @@ async function onModelChange() {
$('#openai_max_context').attr('max', max_2mil); $('#openai_max_context').attr('max', max_2mil);
} else if (value.includes('gemini-exp-1114') || value.includes('gemini-exp-1121')) { } else if (value.includes('gemini-exp-1114') || value.includes('gemini-exp-1121')) {
$('#openai_max_context').attr('max', max_32k); $('#openai_max_context').attr('max', max_32k);
} else if (value.includes('gemini-1.5-pro')) { } else if (value.includes('gemini-1.5-pro') || value.includes('gemini-exp-1206')) {
$('#openai_max_context').attr('max', max_2mil); $('#openai_max_context').attr('max', max_2mil);
} else if (value.includes('gemini-1.5-flash')) { } else if (value.includes('gemini-1.5-flash')) {
$('#openai_max_context').attr('max', max_1mil); $('#openai_max_context').attr('max', max_1mil);
@ -4761,6 +4761,7 @@ export function isImageInliningSupported() {
'gemini-1.5-flash-8b-exp-0924', 'gemini-1.5-flash-8b-exp-0924',
'gemini-exp-1114', 'gemini-exp-1114',
'gemini-exp-1121', 'gemini-exp-1121',
'gemini-exp-1206',
'gemini-1.0-pro-vision-latest', 'gemini-1.0-pro-vision-latest',
'gemini-1.5-pro', 'gemini-1.5-pro',
'gemini-1.5-pro-latest', 'gemini-1.5-pro-latest',

View File

@ -60,7 +60,6 @@ export {
loadMovingUIState, loadMovingUIState,
collapseNewlines, collapseNewlines,
playMessageSound, playMessageSound,
sortEntitiesList,
fixMarkdown, fixMarkdown,
power_user, power_user,
send_on_enter_options, send_on_enter_options,
@ -474,9 +473,9 @@ function switchCompactInputArea() {
$('#compact_input_area').prop('checked', power_user.compact_input_area); $('#compact_input_area').prop('checked', power_user.compact_input_area);
} }
export function switchSwipeNumAllMessages() { function switchSwipeNumAllMessages() {
$('#show_swipe_num_all_messages').prop('checked', power_user.show_swipe_num_all_messages); $('#show_swipe_num_all_messages').prop('checked', power_user.show_swipe_num_all_messages);
$('.mes:not(.last_mes) .swipes-counter').css('opacity', '').toggle(power_user.show_swipe_num_all_messages); $('body').toggleClass('swipeAllMessages', !!power_user.show_swipe_num_all_messages);
} }
var originalSliderValues = []; var originalSliderValues = [];
@ -1838,7 +1837,7 @@ async function loadContextSettings() {
* Common function to perform fuzzy search with optional caching * Common function to perform fuzzy search with optional caching
* @param {string} type - Type of search from fuzzySearchCategories * @param {string} type - Type of search from fuzzySearchCategories
* @param {any[]} data - Data array to search in * @param {any[]} data - Data array to search in
* @param {Array<{name: string, weight: number, getFn?: Function}>} keys - Fuse.js keys configuration * @param {Array<{name: string, weight: number, getFn?: (obj: any) => string}>} keys - Fuse.js keys configuration
* @param {string} searchValue - The search term * @param {string} searchValue - The search term
* @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
* @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score
@ -1852,7 +1851,6 @@ function performFuzzySearch(type, data, keys, searchValue, fuzzySearchCaches = n
} }
} }
// @ts-ignore
const fuse = new Fuse(data, { const fuse = new Fuse(data, {
keys: keys, keys: keys,
includeScore: true, includeScore: true,
@ -2081,19 +2079,22 @@ const compareFunc = (first, second) => {
/** /**
* Sorts an array of entities based on the current sort settings * Sorts an array of entities based on the current sort settings
* @param {any[]} entities An array of objects with an `item` property * @param {any[]} entities An array of objects with an `item` property
* @param {boolean} forceSearch Whether to force search sorting
* @param {import('./filters.js').FilterHelper} [filterHelper=null] Filter helper to use
*/ */
function sortEntitiesList(entities) { export function sortEntitiesList(entities, forceSearch, filterHelper = null) {
filterHelper = filterHelper ?? entitiesFilter;
if (power_user.sort_field == undefined || entities.length === 0) { if (power_user.sort_field == undefined || entities.length === 0) {
return; return;
} }
if (power_user.sort_order === 'random') { const isSearch = forceSearch || $('#character_sort_order option[data-field="search"]').is(':selected');
if (!isSearch && power_user.sort_order === 'random') {
shuffle(entities); shuffle(entities);
return; return;
} }
const isSearch = $('#character_sort_order option[data-field="search"]').is(':selected');
entities.sort((a, b) => { entities.sort((a, b) => {
// Sort tags/folders will always be at the top // Sort tags/folders will always be at the top
if (a.type === 'tag' && b.type !== 'tag') { if (a.type === 'tag' && b.type !== 'tag') {
@ -2105,8 +2106,8 @@ function sortEntitiesList(entities) {
// If we have search sorting, we take scores and use those // If we have search sorting, we take scores and use those
if (isSearch) { if (isSearch) {
const aScore = entitiesFilter.getScore(FILTER_TYPES.SEARCH, `${a.type}.${a.id}`); const aScore = filterHelper.getScore(FILTER_TYPES.SEARCH, `${a.type}.${a.id}`);
const bScore = entitiesFilter.getScore(FILTER_TYPES.SEARCH, `${b.type}.${b.id}`); const bScore = filterHelper.getScore(FILTER_TYPES.SEARCH, `${b.type}.${b.id}`);
return (aScore - bScore); return (aScore - bScore);
} }

View File

@ -52,7 +52,7 @@ import { hideChatMessageRange } from './chats.js';
import { getContext, saveMetadataDebounced } from './extensions.js'; import { getContext, saveMetadataDebounced } from './extensions.js';
import { getRegexedString, regex_placement } from './extensions/regex/engine.js'; import { getRegexedString, regex_placement } from './extensions/regex/engine.js';
import { findGroupMemberId, groups, is_group_generating, openGroupById, resetSelectedGroup, saveGroupChat, selected_group } from './group-chats.js'; import { findGroupMemberId, groups, is_group_generating, openGroupById, resetSelectedGroup, saveGroupChat, selected_group } from './group-chats.js';
import { chat_completion_sources, oai_settings, setupChatCompletionPromptManager } from './openai.js'; import { chat_completion_sources, oai_settings, promptManager } from './openai.js';
import { autoSelectPersona, retriggerFirstMessageOnEmptyChat, setPersonaLockState, togglePersonaLock, user_avatar } from './personas.js'; import { autoSelectPersona, retriggerFirstMessageOnEmptyChat, setPersonaLockState, togglePersonaLock, user_avatar } from './personas.js';
import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js'; import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js';
import { SERVER_INPUTS, textgen_types, textgenerationwebui_settings } from './textgen-settings.js'; import { SERVER_INPUTS, textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
@ -1698,6 +1698,49 @@ export function initDefaultSlashCommands() {
], ],
helpString: 'Sets the model for the current API. Gets the current model name if no argument is provided.', helpString: 'Sets the model for the current API. Gets the current model name if no argument is provided.',
})); }));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'getpromptentry',
aliases: ['getpromptentries'],
callback: getPromptEntryCallback,
returns: 'true/false state of prompt(s)',
namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'identifier',
description: 'Prompt entry identifier(s) to retrieve',
typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST],
acceptsMultiple: true,
enumProvider: () =>
promptManager.serviceSettings.prompts
.map(prompt => prompt.identifier)
.map(identifier => new SlashCommandEnumValue(identifier)),
}),
SlashCommandNamedArgument.fromProps({
name: 'name',
description: 'Prompt entry name(s) to retrieve',
typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST],
acceptsMultiple: true,
enumProvider: () =>
promptManager.serviceSettings.prompts
.map(prompt => prompt.name)
.map(name => new SlashCommandEnumValue(name)),
}),
SlashCommandNamedArgument.fromProps({
name: 'return',
description: 'Whether the return will be simple, a list, or a dict.',
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: 'simple',
enumList: ['simple', 'list', 'dict'],
}),
],
helpString: `
<div>
Gets the state of the specified prompt entries.
</div>
<div>
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.
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'setpromptentry', name: 'setpromptentry',
aliases: ['setpromptentries'], aliases: ['setpromptentries'],
@ -1709,7 +1752,6 @@ export function initDefaultSlashCommands() {
typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST], typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST],
acceptsMultiple: true, acceptsMultiple: true,
enumProvider: () => { enumProvider: () => {
const promptManager = setupChatCompletionPromptManager(oai_settings);
const prompts = promptManager.serviceSettings.prompts; const prompts = promptManager.serviceSettings.prompts;
return prompts.map(prompt => new SlashCommandEnumValue(prompt.identifier, prompt.name, enumTypes.enum)); return prompts.map(prompt => new SlashCommandEnumValue(prompt.identifier, prompt.name, enumTypes.enum));
}, },
@ -1720,7 +1762,6 @@ export function initDefaultSlashCommands() {
typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST], typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST],
acceptsMultiple: true, acceptsMultiple: true,
enumProvider: () => { enumProvider: () => {
const promptManager = setupChatCompletionPromptManager(oai_settings);
const prompts = promptManager.serviceSettings.prompts; const prompts = promptManager.serviceSettings.prompts;
return prompts.map(prompt => new SlashCommandEnumValue(prompt.name, prompt.identifier, enumTypes.enum)); return prompts.map(prompt => new SlashCommandEnumValue(prompt.name, prompt.identifier, enumTypes.enum));
}, },
@ -3786,6 +3827,75 @@ function modelCallback(args, model) {
} }
} }
/**
* Gets the state of prompt entries (toggles) either via identifier/uuid or name.
* @param {object} args Object containing arguments
* @param {string} args.identifier Select prompt entry using an identifier (uuid)
* @param {string} args.name Select prompt entry using name
* @param {string} args.return The type of return value to use (simple, list, dict)
* @returns {Object} An object containing the states of the requested prompt entries
*/
function getPromptEntryCallback(args) {
const prompts = promptManager.serviceSettings.prompts;
let returnType = args.return ?? 'simple';
function parseArgs(arg) {
// Arg is already an array
if (Array.isArray(arg)) {
return arg;
}
const list = [];
try {
// Arg is a JSON-stringified array
const parsedArg = JSON.parse(arg);
list.push(...Array.isArray(parsedArg) ? parsedArg : [arg]);
} catch {
// Arg is a string
list.push(arg);
}
return list;
}
let identifiersList = parseArgs(args.identifier);
let nameList = parseArgs(args.name);
// Check if identifiers exists in prompt, else remove from list
if (identifiersList.length !== 0) {
identifiersList = identifiersList.filter(identifier => prompts.some(prompt => prompt.identifier === identifier));
}
if (nameList.length !== 0) {
nameList.forEach(name => {
let identifiers = prompts
.filter(entry => entry.name === name)
.map(entry => entry.identifier);
identifiersList = identifiersList.concat(identifiers);
});
}
// Get the state for each prompt entry
let promptStates = new Map();
identifiersList.forEach(identifier => {
const promptOrderEntry = promptManager.getPromptOrderEntry(promptManager.activeCharacter, identifier);
if (promptOrderEntry) {
promptStates.set(identifier, promptOrderEntry.enabled);
}
});
// If return is simple (default) but more than one prompt state was retrieved, then change return type
if (returnType === 'simple' && promptStates.size > 1) {
returnType = args.identifier ? 'dict' : 'list';
}
const result = (() => {
if (returnType === 'list') return [...promptStates.values()];
if (returnType === 'dict') return Object.fromEntries(promptStates);
return [...promptStates.values()][0];
})();
return result;
}
/** /**
* Sets state of prompt entries (toggles) either via identifier/uuid or name. * Sets state of prompt entries (toggles) either via identifier/uuid or name.
* @param {object} args Object containing arguments * @param {object} args Object containing arguments
@ -3796,7 +3906,6 @@ function modelCallback(args, model) {
*/ */
function setPromptEntryCallback(args, targetState) { function setPromptEntryCallback(args, targetState) {
// needs promptManager to manipulate prompt entries // needs promptManager to manipulate prompt entries
const promptManager = setupChatCompletionPromptManager(oai_settings);
const prompts = promptManager.serviceSettings.prompts; const prompts = promptManager.serviceSettings.prompts;
function parseArgs(arg) { function parseArgs(arg) {

View File

@ -0,0 +1,171 @@
import {
activateSendButtons,
addOneMessage,
callPopup,
characters,
chat,
chat_metadata,
create_save,
deactivateSendButtons,
event_types,
eventSource,
extension_prompts,
Generate,
generateQuietPrompt,
getCurrentChatId,
getRequestHeaders,
getThumbnailUrl,
main_api,
max_context,
menu_type,
messageFormatting,
name1,
name2,
online_status,
openCharacterChat,
reloadCurrentChat,
renameChat,
saveChatConditional,
saveMetadata,
saveReply,
saveSettingsDebounced,
selectCharacterById,
sendGenerationRequest,
sendStreamingRequest,
sendSystemMessage,
setExtensionPrompt,
stopGeneration,
streamingProcessor,
substituteParams,
substituteParamsExtended,
this_chid,
updateChatMetadata,
} from '../script.js';
import {
extension_settings,
ModuleWorkerWrapper,
renderExtensionTemplate,
renderExtensionTemplateAsync,
writeExtensionField,
} from './extensions.js';
import { groups, openGroupChat, selected_group } from './group-chats.js';
import { t, translate } from './i18n.js';
import { hideLoader, showLoader } from './loader.js';
import { MacrosParser } from './macros.js';
import { oai_settings } from './openai.js';
import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
import { power_user, registerDebugFunction } from './power-user.js';
import { isMobile, shouldSendOnEnter } from './RossAscends-mods.js';
import { ScraperManager } from './scrapers.js';
import { executeSlashCommands, executeSlashCommandsWithOptions, registerSlashCommand } from './slash-commands.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { tag_map, tags } from './tags.js';
import { textgenerationwebui_settings } from './textgen-settings.js';
import { getTokenCount, getTokenCountAsync, getTokenizerModel } from './tokenizers.js';
import { ToolManager } from './tool-calling.js';
import { timestampToMoment } from './utils.js';
export function getContext() {
return {
chat,
characters,
groups,
name1,
name2,
characterId: this_chid,
groupId: selected_group,
chatId: selected_group
? groups.find(x => x.id == selected_group)?.chat_id
: (characters[this_chid]?.chat),
getCurrentChatId,
getRequestHeaders,
reloadCurrentChat,
renameChat,
saveSettingsDebounced,
onlineStatus: online_status,
maxContext: Number(max_context),
chatMetadata: chat_metadata,
streamingProcessor,
eventSource,
eventTypes: event_types,
addOneMessage,
generate: Generate,
sendStreamingRequest,
sendGenerationRequest,
stopGeneration,
/** @deprecated Use getTokenCountAsync instead */
getTokenCount,
getTokenCountAsync,
extensionPrompts: extension_prompts,
setExtensionPrompt,
updateChatMetadata,
saveChat: saveChatConditional,
openCharacterChat,
openGroupChat,
saveMetadata,
sendSystemMessage,
activateSendButtons,
deactivateSendButtons,
saveReply,
substituteParams,
substituteParamsExtended,
SlashCommandParser,
SlashCommand,
SlashCommandArgument,
SlashCommandNamedArgument,
ARGUMENT_TYPE,
executeSlashCommandsWithOptions,
/** @deprecated Use SlashCommandParser.addCommandObject() instead */
registerSlashCommand,
/** @deprecated Use executeSlashCommandWithOptions instead */
executeSlashCommands,
timestampToMoment,
/** @deprecated Handlebars for extensions are no longer supported. */
registerHelper: () => { },
registerMacro: MacrosParser.registerMacro.bind(MacrosParser),
unregisterMacro: MacrosParser.unregisterMacro.bind(MacrosParser),
registerFunctionTool: ToolManager.registerFunctionTool.bind(ToolManager),
unregisterFunctionTool: ToolManager.unregisterFunctionTool.bind(ToolManager),
isToolCallingSupported: ToolManager.isToolCallingSupported.bind(ToolManager),
canPerformToolCalls: ToolManager.canPerformToolCalls.bind(ToolManager),
registerDebugFunction,
/** @deprecated Use renderExtensionTemplateAsync instead. */
renderExtensionTemplate,
renderExtensionTemplateAsync,
registerDataBankScraper: ScraperManager.registerDataBankScraper.bind(ScraperManager),
/** @deprecated Use callGenericPopup or Popup instead. */
callPopup,
callGenericPopup,
showLoader,
hideLoader,
mainApi: main_api,
extensionSettings: extension_settings,
ModuleWorkerWrapper,
getTokenizerModel,
generateQuietPrompt,
writeExtensionField,
getThumbnailUrl,
selectCharacterById,
messageFormatting,
shouldSendOnEnter,
isMobile,
t,
translate,
tags,
tagMap: tag_map,
menuType: menu_type,
createCharacterData: create_save,
/** @deprecated Legacy snake-case naming, compatibility with old extensions */
event_types: event_types,
Popup,
POPUP_TYPE,
POPUP_RESULT,
chatCompletionSettings: oai_settings,
textCompletionSettings: textgenerationwebui_settings,
powerUserSettings: power_user,
};
}
export default getContext;

View File

@ -21,7 +21,8 @@
<li><tt>&lcub;&lcub;user&rcub;&rcub;</tt> <span data-i18n="help_macros_15">your current Persona username</span></li> <li><tt>&lcub;&lcub;user&rcub;&rcub;</tt> <span data-i18n="help_macros_15">your current Persona username</span></li>
<li><tt>&lcub;&lcub;char&rcub;&rcub;</tt> <span data-i18n="help_macros_16">the Character's name</span></li> <li><tt>&lcub;&lcub;char&rcub;&rcub;</tt> <span data-i18n="help_macros_16">the Character's name</span></li>
<li><tt>&lcub;&lcub;char_version&rcub;&rcub;</tt> <span data-i18n="help_macros_17">the Character's version number</span></li> <li><tt>&lcub;&lcub;char_version&rcub;&rcub;</tt> <span data-i18n="help_macros_17">the Character's version number</span></li>
<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> <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>
<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>
<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> <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>
<li><tt>&lcub;&lcub;lastMessage&rcub;&rcub;</tt> <span data-i18n="help_macros_20">the text of the latest chat message.</span></li> <li><tt>&lcub;&lcub;lastMessage&rcub;&rcub;</tt> <span data-i18n="help_macros_20">the text of the latest chat message.</span></li>
<li><tt>&lcub;&lcub;lastUserMessage&rcub;&rcub;</tt> <span data-i18n="help_macros_lastUser">the text of the latest user chat message.</span></li> <li><tt>&lcub;&lcub;lastUserMessage&rcub;&rcub;</tt> <span data-i18n="help_macros_lastUser">the text of the latest user chat message.</span></li>

View File

@ -18,13 +18,6 @@ import { getCurrentDreamGenModelTokenizer, getCurrentOpenRouterModelTokenizer }
import { ENCODE_TOKENIZERS, TEXTGEN_TOKENIZERS, getTextTokens, tokenizers } from './tokenizers.js'; import { ENCODE_TOKENIZERS, TEXTGEN_TOKENIZERS, getTextTokens, tokenizers } from './tokenizers.js';
import { getSortableDelay, onlyUnique } from './utils.js'; import { getSortableDelay, onlyUnique } from './utils.js';
export {
settings as textgenerationwebui_settings,
loadTextGenSettings,
generateTextGenWithStreaming,
formatTextGenURL,
};
export const textgen_types = { export const textgen_types = {
OOBA: 'ooba', OOBA: 'ooba',
MANCER: 'mancer', MANCER: 'mancer',
@ -197,6 +190,10 @@ const settings = {
featherless_model: '', featherless_model: '',
}; };
export {
settings as textgenerationwebui_settings,
};
export let textgenerationwebui_banned_in_macros = []; export let textgenerationwebui_banned_in_macros = [];
export let textgenerationwebui_presets = []; export let textgenerationwebui_presets = [];
@ -327,7 +324,7 @@ async function selectPreset(name) {
saveSettingsDebounced(); saveSettingsDebounced();
} }
function formatTextGenURL(value) { export function formatTextGenURL(value) {
try { try {
const noFormatTypes = [MANCER, TOGETHERAI, INFERMATICAI, DREAMGEN, OPENROUTER]; const noFormatTypes = [MANCER, TOGETHERAI, INFERMATICAI, DREAMGEN, OPENROUTER];
if (noFormatTypes.includes(settings.type)) { if (noFormatTypes.includes(settings.type)) {
@ -465,7 +462,7 @@ function calculateLogitBias() {
return result; return result;
} }
function loadTextGenSettings(data, loadedSettings) { export function loadTextGenSettings(data, loadedSettings) {
textgenerationwebui_presets = convertPresets(data.textgenerationwebui_presets); textgenerationwebui_presets = convertPresets(data.textgenerationwebui_presets);
textgenerationwebui_preset_names = data.textgenerationwebui_preset_names ?? []; textgenerationwebui_preset_names = data.textgenerationwebui_preset_names ?? [];
Object.assign(settings, loadedSettings.textgenerationwebui_settings ?? {}); Object.assign(settings, loadedSettings.textgenerationwebui_settings ?? {});
@ -889,7 +886,7 @@ function setSettingByName(setting, value, trigger) {
* @returns {Promise<(function(): AsyncGenerator<{swipes: [], text: string, toolCalls: [], logprobs: {token: string, topLogprobs: Candidate[]}|null}, void, *>)|*>} * @returns {Promise<(function(): AsyncGenerator<{swipes: [], text: string, toolCalls: [], logprobs: {token: string, topLogprobs: Candidate[]}|null}, void, *>)|*>}
* @throws {Error} - If the response status is not OK, or from within the generator * @throws {Error} - If the response status is not OK, or from within the generator
*/ */
async function generateTextGenWithStreaming(generate_data, signal) { export async function generateTextGenWithStreaming(generate_data, signal) {
generate_data.stream = true; generate_data.stream = true;
const response = await fetch('/api/backends/text-completions/generate', { const response = await fetch('/api/backends/text-completions/generate', {

View File

@ -8,8 +8,6 @@ import { kai_flags } from './kai-settings.js';
import { textgen_types, textgenerationwebui_settings as textgen_settings, getTextGenServer, getTextGenModel } from './textgen-settings.js'; import { textgen_types, textgenerationwebui_settings as textgen_settings, getTextGenServer, getTextGenModel } from './textgen-settings.js';
import { getCurrentDreamGenModelTokenizer, getCurrentOpenRouterModelTokenizer, openRouterModels } from './textgen-models.js'; import { getCurrentDreamGenModelTokenizer, getCurrentOpenRouterModelTokenizer, openRouterModels } from './textgen-models.js';
const { OOBA, TABBY, KOBOLDCPP, VLLM, APHRODITE, LLAMACPP, OPENROUTER, DREAMGEN } = textgen_types;
export const CHARACTERS_PER_TOKEN_RATIO = 3.35; export const CHARACTERS_PER_TOKEN_RATIO = 3.35;
export const TOKENIZER_WARNING_KEY = 'tokenizationWarningShown'; export const TOKENIZER_WARNING_KEY = 'tokenizationWarningShown';
export const TOKENIZER_SUPPORTED_KEY = 'tokenizationSupported'; export const TOKENIZER_SUPPORTED_KEY = 'tokenizationSupported';
@ -52,8 +50,12 @@ export const ENCODE_TOKENIZERS = [
//tokenizers.NERD2, //tokenizers.NERD2,
]; ];
// A list of Text Completion sources that support remote tokenization. /**
export const TEXTGEN_TOKENIZERS = [OOBA, TABBY, KOBOLDCPP, LLAMACPP, VLLM, APHRODITE]; * A list of Text Completion sources that support remote tokenization.
* Populated in initTokenziers due to circular dependencies.
* @type {string[]}
*/
export const TEXTGEN_TOKENIZERS = [];
const TOKENIZER_URLS = { const TOKENIZER_URLS = {
[tokenizers.GPT2]: { [tokenizers.GPT2]: {
@ -287,7 +289,7 @@ export function getTokenizerBestMatch(forApi) {
const hasTokenizerError = sessionStorage.getItem(TOKENIZER_WARNING_KEY); const hasTokenizerError = sessionStorage.getItem(TOKENIZER_WARNING_KEY);
const hasValidEndpoint = sessionStorage.getItem(TOKENIZER_SUPPORTED_KEY); const hasValidEndpoint = sessionStorage.getItem(TOKENIZER_SUPPORTED_KEY);
const isConnected = online_status !== 'no_connection'; const isConnected = online_status !== 'no_connection';
const isTokenizerSupported = TEXTGEN_TOKENIZERS.includes(textgen_settings.type) && (textgen_settings.type !== OOBA || hasValidEndpoint); const isTokenizerSupported = TEXTGEN_TOKENIZERS.includes(textgen_settings.type) && (textgen_settings.type !== textgen_types.OOBA || hasValidEndpoint);
if (!hasTokenizerError && isConnected) { if (!hasTokenizerError && isConnected) {
if (forApi === 'kobold' && kai_flags.can_use_tokenization) { if (forApi === 'kobold' && kai_flags.can_use_tokenization) {
@ -297,10 +299,10 @@ export function getTokenizerBestMatch(forApi) {
if (forApi === 'textgenerationwebui' && isTokenizerSupported) { if (forApi === 'textgenerationwebui' && isTokenizerSupported) {
return tokenizers.API_TEXTGENERATIONWEBUI; return tokenizers.API_TEXTGENERATIONWEBUI;
} }
if (forApi === 'textgenerationwebui' && textgen_settings.type === OPENROUTER) { if (forApi === 'textgenerationwebui' && textgen_settings.type === textgen_types.OPENROUTER) {
return getCurrentOpenRouterModelTokenizer(); return getCurrentOpenRouterModelTokenizer();
} }
if (forApi === 'textgenerationwebui' && textgen_settings.type === DREAMGEN) { if (forApi === 'textgenerationwebui' && textgen_settings.type === textgen_types.DREAMGEN) {
return getCurrentDreamGenModelTokenizer(); return getCurrentDreamGenModelTokenizer();
} }
} }
@ -576,7 +578,7 @@ export function getTokenizerModel() {
// And for OpenRouter (if not a site model, then it's impossible to determine the tokenizer) // And for OpenRouter (if not a site model, then it's impossible to determine the tokenizer)
if (main_api == 'openai' && oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER && oai_settings.openrouter_model || if (main_api == 'openai' && oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER && oai_settings.openrouter_model ||
main_api == 'textgenerationwebui' && textgen_settings.type === OPENROUTER && textgen_settings.openrouter_model) { main_api == 'textgenerationwebui' && textgen_settings.type === textgen_types.OPENROUTER && textgen_settings.openrouter_model) {
const model = main_api == 'openai' const model = main_api == 'openai'
? model_list.find(x => x.id === oai_settings.openrouter_model) ? model_list.find(x => x.id === oai_settings.openrouter_model)
: openRouterModels.find(x => x.id === textgen_settings.openrouter_model); : openRouterModels.find(x => x.id === textgen_settings.openrouter_model);
@ -652,7 +654,7 @@ export function getTokenizerModel() {
return oai_settings.custom_model; return oai_settings.custom_model;
} }
if (oai_settings.chat_completion_source === chat_completion_sources.PERPLEXITY) { if (oai_settings.chat_completion_source === chat_completion_sources.PERPLEXITY) {
if (oai_settings.perplexity_model.includes('llama-3') || oai_settings.perplexity_model.includes('llama3')) { if (oai_settings.perplexity_model.includes('llama-3') || oai_settings.perplexity_model.includes('llama3')) {
return llama3Tokenizer; return llama3Tokenizer;
} }
@ -680,7 +682,7 @@ export function getTokenizerModel() {
return yiTokenizer; return yiTokenizer;
} }
if (oai_settings.chat_completion_source === chat_completion_sources.BLOCKENTROPY) { if (oai_settings.chat_completion_source === chat_completion_sources.BLOCKENTROPY) {
if (oai_settings.blockentropy_model.includes('llama3')) { if (oai_settings.blockentropy_model.includes('llama3')) {
return llama3Tokenizer; return llama3Tokenizer;
} }
@ -1121,6 +1123,14 @@ export function decodeTextTokens(tokenizerType, ids) {
} }
export async function initTokenizers() { export async function initTokenizers() {
TEXTGEN_TOKENIZERS.push(
textgen_types.OOBA,
textgen_types.TABBY,
textgen_types.KOBOLDCPP,
textgen_types.LLAMACPP,
textgen_types.VLLM,
textgen_types.APHRODITE,
);
await loadTokenCache(); await loadTokenCache();
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); 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);
} }

View File

@ -947,7 +947,7 @@ export class ToolManager {
enumProvider: toolsEnumProvider, enumProvider: toolsEnumProvider,
}), }),
], ],
callback: async (name) => { callback: async (_, name) => {
if (typeof name !== 'string' || !name) { if (typeof name !== 'string' || !name) {
throw new Error('The unnamed argument must be a non-empty string.'); throw new Error('The unnamed argument must be a non-empty string.');
} }

View File

@ -1040,8 +1040,14 @@ body .panelControlBar {
height: var(--swipeCounterHeight); height: var(--swipeCounterHeight);
} }
.mes:not(.last_mes) .swipes-counter { body:not(.swipeAllMessages) .mes:not(.last_mes) .swipes-counter {
opacity: 0.3; visibility: hidden;
}
body.swipeAllMessages .mes:not(.last_mes) .swipes-counter {
/* Avoid expensive DOM queries */
opacity: 0.3 !important;
display: flex !important;
} }
.swipe_left { .swipe_left {

View File

@ -280,7 +280,7 @@ async function sendMakerSuiteRequest(request, response) {
delete generationConfig.stopSequences; delete generationConfig.stopSequences;
} }
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; 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;
const prompt = convertGooglePrompt(request.body.messages, model, should_use_system_prompt, request.body.char_name, request.body.user_name); const prompt = convertGooglePrompt(request.body.messages, model, should_use_system_prompt, request.body.char_name, request.body.user_name);
let body = { let body = {
contents: prompt.contents, contents: prompt.contents,

View File

@ -14,7 +14,7 @@ import jimp from 'jimp';
import { AVATAR_WIDTH, AVATAR_HEIGHT } from '../constants.js'; import { AVATAR_WIDTH, AVATAR_HEIGHT } from '../constants.js';
import { jsonParser, urlencodedParser } from '../express-common.js'; import { jsonParser, urlencodedParser } from '../express-common.js';
import { deepMerge, humanizedISO8601DateTime, tryParse, extractFileFromZipBuffer, MemoryLimitedMap } from '../util.js'; import { deepMerge, humanizedISO8601DateTime, tryParse, extractFileFromZipBuffer, MemoryLimitedMap, getConfigValue } from '../util.js';
import { TavernCardValidator } from '../validator/TavernCardValidator.js'; import { TavernCardValidator } from '../validator/TavernCardValidator.js';
import { parse, write } from '../character-card-parser.js'; import { parse, write } from '../character-card-parser.js';
import { readWorldInfoFile } from './worldinfo.js'; import { readWorldInfoFile } from './worldinfo.js';
@ -23,8 +23,9 @@ import { importRisuSprites } from './sprites.js';
const defaultAvatarPath = './public/img/ai4.png'; const defaultAvatarPath = './public/img/ai4.png';
// KV-store for parsed character data // KV-store for parsed character data
// 100 MB limit. Would take roughly 3000 characters to reach this limit const cacheCapacity = Number(getConfigValue('cardsCacheCapacity', 100)); // MB
const characterDataCache = new MemoryLimitedMap(1024 * 1024 * 100); // With 100 MB limit it would take roughly 3000 characters to reach this limit
const characterDataCache = new MemoryLimitedMap(1024 * 1024 * cacheCapacity);
// Some Android devices require tighter memory management // Some Android devices require tighter memory management
const isAndroid = process.platform === 'android'; const isAndroid = process.platform === 'android';

View File

@ -203,19 +203,19 @@ function importKoboldLiteChat(_userName, _characterName, data) {
/** @type {function(string): object} */ /** @type {function(string): object} */
function processKoboldMessage(msg) { function processKoboldMessage(msg) {
const isUser = msg.includes(inputToken) || msg.includes(outputToken); const isUser = msg.includes(inputToken);
return { return {
name: isUser ? header.user_name : header.character_name, name: isUser ? header.user_name : header.character_name,
is_user: isUser, is_user: isUser,
mes: msg.replace(inputToken, '').replace(outputToken, '').trim(), mes: msg.replaceAll(inputToken, '').replaceAll(outputToken, '').trim(),
send_date: Date.now(), send_date: Date.now(),
}; };
} }
// Create the header // Create the header
const header = { const header = {
user_name: data.savedsettings.chatname, user_name: String(data.savedsettings.chatname),
character_name: data.savedsettings.chatopponent, character_name: String(data.savedsettings.chatopponent).split('||$||')[0],
}; };
// Format messages // Format messages
const formattedMessages = data.actions.map(processKoboldMessage); const formattedMessages = data.actions.map(processKoboldMessage);

View File

@ -67,17 +67,17 @@ router.post('/login', jsonParser, async (request, response) => {
const user = await storage.getItem(toKey(request.body.handle)); const user = await storage.getItem(toKey(request.body.handle));
if (!user) { if (!user) {
console.log('Login failed: User not found'); console.log('Login failed: User', request.body.handle, 'not found');
return response.status(403).json({ error: 'Incorrect credentials' }); return response.status(403).json({ error: 'Incorrect credentials' });
} }
if (!user.enabled) { if (!user.enabled) {
console.log('Login failed: User is disabled'); console.log('Login failed: User', user.handle, 'is disabled');
return response.status(403).json({ error: 'User is disabled' }); return response.status(403).json({ error: 'User is disabled' });
} }
if (user.password && user.password !== getPasswordHash(request.body.password, user.salt)) { if (user.password && user.password !== getPasswordHash(request.body.password, user.salt)) {
console.log('Login failed: Incorrect password'); console.log('Login failed: Incorrect password for', user.handle);
return response.status(403).json({ error: 'Incorrect credentials' }); return response.status(403).json({ error: 'Incorrect credentials' });
} }
@ -88,7 +88,7 @@ router.post('/login', jsonParser, async (request, response) => {
await loginLimiter.delete(ip); await loginLimiter.delete(ip);
request.session.handle = user.handle; request.session.handle = user.handle;
console.log('Login successful:', user.handle, request.session); console.log('Login successful:', user.handle, 'from', ip, 'at', new Date().toLocaleString());
return response.json({ handle: user.handle }); return response.json({ handle: user.handle });
} catch (error) { } catch (error) {
if (error instanceof RateLimiterRes) { if (error instanceof RateLimiterRes) {
@ -115,12 +115,12 @@ router.post('/recover-step1', jsonParser, async (request, response) => {
const user = await storage.getItem(toKey(request.body.handle)); const user = await storage.getItem(toKey(request.body.handle));
if (!user) { if (!user) {
console.log('Recover step 1 failed: User not found'); console.log('Recover step 1 failed: User', request.body.handle, 'not found');
return response.status(404).json({ error: 'User not found' }); return response.status(404).json({ error: 'User not found' });
} }
if (!user.enabled) { if (!user.enabled) {
console.log('Recover step 1 failed: User is disabled'); console.log('Recover step 1 failed: User', user.handle, 'is disabled');
return response.status(403).json({ error: 'User is disabled' }); return response.status(403).json({ error: 'User is disabled' });
} }
@ -153,12 +153,12 @@ router.post('/recover-step2', jsonParser, async (request, response) => {
const ip = getIpFromRequest(request); const ip = getIpFromRequest(request);
if (!user) { if (!user) {
console.log('Recover step 2 failed: User not found'); console.log('Recover step 2 failed: User', request.body.handle, 'not found');
return response.status(404).json({ error: 'User not found' }); return response.status(404).json({ error: 'User not found' });
} }
if (!user.enabled) { if (!user.enabled) {
console.log('Recover step 2 failed: User is disabled'); console.log('Recover step 2 failed: User', user.handle, 'is disabled');
return response.status(403).json({ error: 'User is disabled' }); return response.status(403).json({ error: 'User is disabled' });
} }

View File

@ -347,6 +347,7 @@ export function convertGooglePrompt(messages, model, useSysPrompt = false, charN
'gemini-1.5-flash-8b-exp-0924', 'gemini-1.5-flash-8b-exp-0924',
'gemini-exp-1114', 'gemini-exp-1114',
'gemini-exp-1121', 'gemini-exp-1121',
'gemini-exp-1206',
'gemini-1.5-pro', 'gemini-1.5-pro',
'gemini-1.5-pro-latest', 'gemini-1.5-pro-latest',
'gemini-1.5-pro-001', 'gemini-1.5-pro-001',

View File

@ -680,8 +680,9 @@ export class MemoryLimitedMap {
* @param {number} maxMemoryInBytes - The maximum allowed memory in bytes for string values. * @param {number} maxMemoryInBytes - The maximum allowed memory in bytes for string values.
*/ */
constructor(maxMemoryInBytes) { constructor(maxMemoryInBytes) {
if (typeof maxMemoryInBytes !== 'number' || maxMemoryInBytes <= 0) { if (typeof maxMemoryInBytes !== 'number' || maxMemoryInBytes <= 0 || isNaN(maxMemoryInBytes)) {
throw new Error('maxMemoryInBytes must be a positive number'); console.warn('Invalid maxMemoryInBytes, using a fallback value of 1 GB.');
maxMemoryInBytes = 1024 * 1024 * 1024; // 1 GB
} }
this.maxMemory = maxMemoryInBytes; this.maxMemory = maxMemoryInBytes;
this.currentMemory = 0; this.currentMemory = 0;