From 62342b35e27253512352f8be5142dbb0c7063d47 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sun, 16 Mar 2025 15:01:31 +0200
Subject: [PATCH 01/21] Reasoning template
---
default/content/index.json | 4 +
.../presets/reasoning/DeepSeek R1.json | 6 +
public/index.html | 13 ++
public/scripts/power-user.js | 3 +
public/scripts/preset-manager.js | 46 ++++-
public/scripts/reasoning.js | 171 ++++++++++++++++--
src/constants.js | 1 +
src/endpoints/content-manager.js | 5 +-
src/endpoints/presets.js | 2 +
src/endpoints/settings.js | 2 +
src/users.js | 1 +
11 files changed, 234 insertions(+), 20 deletions(-)
create mode 100644 default/content/presets/reasoning/DeepSeek R1.json
diff --git a/default/content/index.json b/default/content/index.json
index 4caa21c14..3a09e8e84 100644
--- a/default/content/index.json
+++ b/default/content/index.json
@@ -786,5 +786,9 @@
{
"filename": "presets/context/DeepSeek-V2.5.json",
"type": "context"
+ },
+ {
+ "filename": "presets/reasoning/DeepSeek R1.json",
+ "type": "reasoning"
}
]
diff --git a/default/content/presets/reasoning/DeepSeek R1.json b/default/content/presets/reasoning/DeepSeek R1.json
new file mode 100644
index 000000000..503c45171
--- /dev/null
+++ b/default/content/presets/reasoning/DeepSeek R1.json
@@ -0,0 +1,6 @@
+{
+ "name": "DeepSeek R1",
+ "prefix": "\n",
+ "suffix": "\n ",
+ "separator": "\n\n"
+}
diff --git a/public/index.html b/public/index.html
index 0753e3134..a072aaf47 100644
--- a/public/index.html
+++ b/public/index.html
@@ -3917,6 +3917,19 @@
Reasoning Formatting
+
Prefix
diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js
index 572be339b..ba5dead91 100644
--- a/public/scripts/power-user.js
+++ b/public/scripts/power-user.js
@@ -55,6 +55,7 @@ import { POPUP_TYPE, callGenericPopup } from './popup.js';
import { loadSystemPrompts } from './sysprompt.js';
import { fuzzySearchCategories } from './filters.js';
import { accountStorage } from './util/AccountStorage.js';
+import { loadReasoningTemplates } from './reasoning.js';
export {
loadPowerUserSettings,
@@ -255,6 +256,7 @@ let power_user = {
},
reasoning: {
+ name: 'DeepSeek R1',
auto_parse: false,
add_to_prompts: false,
auto_expand: false,
@@ -1622,6 +1624,7 @@ async function loadPowerUserSettings(settings, data) {
await loadInstructMode(data);
await loadContextSettings();
await loadSystemPrompts(data);
+ await loadReasoningTemplates(data);
loadMaxContextUnlocked();
switchWaifuMode();
switchSpoilerMode();
diff --git a/public/scripts/preset-manager.js b/public/scripts/preset-manager.js
index c582497fa..cdff9501c 100644
--- a/public/scripts/preset-manager.js
+++ b/public/scripts/preset-manager.js
@@ -21,7 +21,7 @@ import { groups, selected_group } from './group-chats.js';
import { instruct_presets } from './instruct-mode.js';
import { kai_settings } from './kai-settings.js';
import { convertNovelPreset } from './nai-settings.js';
-import { openai_settings, openai_setting_names, oai_settings } from './openai.js';
+import { openai_settings, openai_setting_names } from './openai.js';
import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
import { context_presets, getContextSettings, power_user } from './power-user.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
@@ -38,6 +38,7 @@ import {
} from './textgen-settings.js';
import { download, parseJsonFile, waitUntilCondition } from './utils.js';
import { t } from './i18n.js';
+import { reasoning_templates } from './reasoning.js';
const presetManagers = {};
@@ -168,6 +169,20 @@ class PresetManager {
},
isValid: (data) => PresetManager.isPossiblyTextCompletionData(data),
},
+ 'reasoning': {
+ name: 'Reasoning Formatting',
+ getData: () => {
+ const manager = getPresetManager('reasoning');
+ const name = manager.getSelectedPresetName();
+ return manager.getPresetSettings(name);
+ },
+ setData: (data) => {
+ const manager = getPresetManager('reasoning');
+ const name = data.name;
+ return manager.savePreset(name, data);
+ },
+ isValid: (data) => PresetManager.isPossiblyReasoningData(data),
+ },
};
static isPossiblyInstructData(data) {
@@ -190,6 +205,11 @@ class PresetManager {
return data && textCompletionProps.every(prop => Object.keys(data).includes(prop));
}
+ static isPossiblyReasoningData(data) {
+ const reasoningProps = ['name', 'prefix', 'suffix', 'separator'];
+ return data && reasoningProps.every(prop => Object.keys(data).includes(prop));
+ }
+
/**
* Imports master settings from JSON data.
* @param {object} data Data to import
@@ -227,6 +247,12 @@ class PresetManager {
return await getPresetManager('textgenerationwebui').savePreset(fileName, data);
}
+ // 5. Reasoning Template
+ if (this.isPossiblyReasoningData(data)) {
+ toastr.info(t`Importing as reasoning template...`, t`Reasoning template detected`);
+ return await getPresetManager('reasoning').savePreset(data.name, data);
+ }
+
const validSections = [];
for (const [key, section] of Object.entries(this.masterSections)) {
if (key in data && section.isValid(data[key])) {
@@ -478,6 +504,10 @@ class PresetManager {
presets = system_prompts;
preset_names = system_prompts.map(x => x.name);
break;
+ case 'reasoning':
+ presets = reasoning_templates;
+ preset_names = reasoning_templates.map(x => x.name);
+ break;
default:
console.warn(`Unknown API ID ${api}`);
}
@@ -490,7 +520,7 @@ class PresetManager {
}
isAdvancedFormatting() {
- return this.apiId == 'context' || this.apiId == 'instruct' || this.apiId == 'sysprompt';
+ return ['context', 'instruct', 'sysprompt', 'reasoning'].includes(this.apiId);
}
updateList(name, preset) {
@@ -553,6 +583,11 @@ class PresetManager {
sysprompt_preset['name'] = name || power_user.sysprompt.preset;
return sysprompt_preset;
}
+ case 'reasoning': {
+ const reasoning_preset = structuredClone(power_user.reasoning);
+ reasoning_preset['name'] = name || power_user.reasoning.preset;
+ return reasoning_preset;
+ }
default:
console.warn(`Unknown API ID ${apiId}`);
return {};
@@ -599,6 +634,13 @@ class PresetManager {
'include_reasoning',
'global_banned_tokens',
'send_banned_tokens',
+
+ // Reasoning exclusions
+ 'auto_parse',
+ 'add_to_prompts',
+ 'auto_expand',
+ 'show_hidden',
+ 'max_additions',
];
const settings = Object.assign({}, getSettingsByApiId(this.apiId));
diff --git a/public/scripts/reasoning.js b/public/scripts/reasoning.js
index 2a8e9f73e..a4f663ff3 100644
--- a/public/scripts/reasoning.js
+++ b/public/scripts/reasoning.js
@@ -1,4 +1,5 @@
import {
+ Fuse,
moment,
} from '../lib.js';
import { chat, closeMessageEditor, event_types, eventSource, main_api, messageFormatting, saveChatConditional, saveChatDebounced, saveSettingsDebounced, substituteParams, syncMesToSwipe, updateMessageBlock } from '../script.js';
@@ -14,7 +15,36 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom
import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
-import { copyText, escapeRegex, isFalseBoolean, setDatasetProperty, trimSpaces } from './utils.js';
+import { copyText, escapeRegex, isFalseBoolean, isTrueBoolean, setDatasetProperty, trimSpaces } from './utils.js';
+
+/**
+ * @typedef {object} ReasoningTemplate
+ * @property {string} name - The name of the template
+ * @property {string} prefix - Reasoning prefix
+ * @property {string} suffix - Reasoning suffix
+ * @property {string} separator - Reasoning separator
+ */
+
+/**
+ * @type {ReasoningTemplate[]} List of reasoning templates
+ */
+export const reasoning_templates = [];
+
+/**
+ * @type {Record
>} List of UI elements for reasoning settings
+ * @readonly
+ */
+const UI = {
+ $select: $('#reasoning_select'),
+ $suffix: $('#reasoning_suffix'),
+ $prefix: $('#reasoning_prefix'),
+ $separator: $('#reasoning_separator'),
+ $autoParse: $('#reasoning_auto_parse'),
+ $autoExpand: $('#reasoning_auto_expand'),
+ $showHidden: $('#reasoning_show_hidden'),
+ $addToPrompts: $('#reasoning_add_to_prompts'),
+ $maxAdditions: $('#reasoning_max_additions'),
+};
/**
* Enum representing the type of the reasoning for a message (where it came from)
@@ -664,57 +694,103 @@ export class PromptReasoning {
}
function loadReasoningSettings() {
- $('#reasoning_add_to_prompts').prop('checked', power_user.reasoning.add_to_prompts);
- $('#reasoning_add_to_prompts').on('change', function () {
+ UI.$addToPrompts.prop('checked', power_user.reasoning.add_to_prompts);
+ UI.$addToPrompts.on('change', function () {
power_user.reasoning.add_to_prompts = !!$(this).prop('checked');
saveSettingsDebounced();
});
- $('#reasoning_prefix').val(power_user.reasoning.prefix);
- $('#reasoning_prefix').on('input', function () {
+ UI.$prefix.val(power_user.reasoning.prefix);
+ UI.$prefix.on('input', function () {
power_user.reasoning.prefix = String($(this).val());
saveSettingsDebounced();
});
- $('#reasoning_suffix').val(power_user.reasoning.suffix);
- $('#reasoning_suffix').on('input', function () {
+ UI.$suffix.val(power_user.reasoning.suffix);
+ UI.$suffix.on('input', function () {
power_user.reasoning.suffix = String($(this).val());
saveSettingsDebounced();
});
- $('#reasoning_separator').val(power_user.reasoning.separator);
- $('#reasoning_separator').on('input', function () {
+ UI.$separator.val(power_user.reasoning.separator);
+ UI.$separator.on('input', function () {
power_user.reasoning.separator = String($(this).val());
saveSettingsDebounced();
});
- $('#reasoning_max_additions').val(power_user.reasoning.max_additions);
- $('#reasoning_max_additions').on('input', function () {
+ UI.$maxAdditions.val(power_user.reasoning.max_additions);
+ UI.$maxAdditions.on('input', function () {
power_user.reasoning.max_additions = Number($(this).val());
saveSettingsDebounced();
});
- $('#reasoning_auto_parse').prop('checked', power_user.reasoning.auto_parse);
- $('#reasoning_auto_parse').on('change', function () {
+ UI.$autoParse.prop('checked', power_user.reasoning.auto_parse);
+ UI.$autoParse.on('change', function () {
power_user.reasoning.auto_parse = !!$(this).prop('checked');
saveSettingsDebounced();
});
- $('#reasoning_auto_expand').prop('checked', power_user.reasoning.auto_expand);
- $('#reasoning_auto_expand').on('change', function () {
+ UI.$autoExpand.prop('checked', power_user.reasoning.auto_expand);
+ UI.$autoExpand.on('change', function () {
power_user.reasoning.auto_expand = !!$(this).prop('checked');
toggleReasoningAutoExpand();
saveSettingsDebounced();
});
toggleReasoningAutoExpand();
- $('#reasoning_show_hidden').prop('checked', power_user.reasoning.show_hidden);
- $('#reasoning_show_hidden').on('change', function () {
+ UI.$showHidden.prop('checked', power_user.reasoning.show_hidden);
+ UI.$showHidden.on('change', function () {
power_user.reasoning.show_hidden = !!$(this).prop('checked');
$('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);
saveSettingsDebounced();
});
$('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);
+
+ UI.$select.on('change', async function () {
+ const name = String($(this).val());
+ const template = reasoning_templates.find(p => p.name === name);
+ if (!template) {
+ return;
+ }
+
+ UI.$prefix.val(template.prefix);
+ UI.$suffix.val(template.suffix);
+ UI.$separator.val(template.separator);
+
+ power_user.reasoning.name = name;
+ power_user.reasoning.prefix = template.prefix;
+ power_user.reasoning.suffix = template.suffix;
+ power_user.reasoning.separator = template.separator;
+
+ saveSettingsDebounced();
+ });
+}
+
+function selectReasoningTemplateCallback(args, name) {
+ if (!name) {
+ return power_user.reasoning.name ?? '';
+ }
+
+ const quiet = isTrueBoolean(args?.quiet);
+ const templateNames = reasoning_templates.map(preset => preset.name);
+ let foundName = templateNames.find(x => x.toLowerCase() === name.toLowerCase());
+
+ if (!foundName) {
+ const fuse = new Fuse(templateNames);
+ const result = fuse.search(name);
+
+ if (result.length === 0) {
+ !quiet && toastr.warning(`Reasoning template "${name}" not found`);
+ return '';
+ }
+
+ foundName = result[0].item;
+ }
+
+ UI.$select.val(foundName).trigger('change');
+ !quiet && toastr.success(`Reasoning template "${foundName}" selected`);
+ return foundName;
+
}
function registerReasoningSlashCommands() {
@@ -848,6 +924,42 @@ function registerReasoningSlashCommands() {
: parsedReasoning.reasoning;
},
}));
+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
+ name: 'reasoning-template',
+ aliases: ['reasoning-formatting', 'reasoning-preset'],
+ callback: selectReasoningTemplateCallback,
+ returns: 'template name',
+ namedArgumentList: [
+ SlashCommandNamedArgument.fromProps({
+ name: 'quiet',
+ description: 'Suppress the toast message on template change',
+ typeList: [ARGUMENT_TYPE.BOOLEAN],
+ defaultValue: 'false',
+ enumList: commonEnumProviders.boolean('trueFalse')(),
+ }),
+ ],
+ unnamedArgumentList: [
+ SlashCommandArgument.fromProps({
+ description: 'reasoning template name',
+ typeList: [ARGUMENT_TYPE.STRING],
+ enumProvider: () => reasoning_templates.map(x => new SlashCommandEnumValue(x.name, null, enumTypes.enum, enumIcons.preset)),
+ }),
+ ],
+ helpString: `
+
+ Selects a reasoning template by name, using fuzzy search to find the closest match.
+ Gets the current template if no name is provided.
+
+
+ `,
+ }));
}
function registerReasoningMacros() {
@@ -1207,6 +1319,31 @@ function registerReasoningAppEvents() {
}
}
+/**
+ * Loads reasoning templates from the settings data.
+ * @param {object} data Settings data
+ * @param {ReasoningTemplate[]} data.reasoning Reasoning templates
+ * @returns {Promise}
+ */
+export async function loadReasoningTemplates(data) {
+ if (data.reasoning !== undefined) {
+ reasoning_templates.splice(0, reasoning_templates.length, ...data.reasoning);
+ }
+
+ for (const template of reasoning_templates) {
+ $('').val(template.name).text(template.name).appendTo(UI.$select);
+ }
+
+ if (!power_user.reasoning.name) {
+ power_user.reasoning.name = reasoning_templates[0]?.name ?? '';
+ }
+
+ UI.$select.val(power_user.reasoning.name);
+}
+
+/**
+ * Initializes reasoning settings and event handlers.
+ */
export function initReasoning() {
loadReasoningSettings();
setReasoningEventHandlers();
diff --git a/src/constants.js b/src/constants.js
index 66697fed4..d06a809c6 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -43,6 +43,7 @@ export const USER_DIRECTORY_TEMPLATE = Object.freeze({
vectors: 'vectors',
backups: 'backups',
sysprompt: 'sysprompt',
+ reasoning: 'reasoning',
});
/**
diff --git a/src/endpoints/content-manager.js b/src/endpoints/content-manager.js
index 898f019ee..6cebf53b7 100644
--- a/src/endpoints/content-manager.js
+++ b/src/endpoints/content-manager.js
@@ -48,6 +48,7 @@ export const CONTENT_TYPES = {
MOVING_UI: 'moving_ui',
QUICK_REPLIES: 'quick_replies',
SYSPROMPT: 'sysprompt',
+ REASONING: 'reasoning',
};
/**
@@ -61,7 +62,7 @@ export function getDefaultPresets(directories) {
const presets = [];
for (const contentItem of contentIndex) {
- if (contentItem.type.endsWith('_preset') || contentItem.type === 'instruct' || contentItem.type === 'context' || contentItem.type === 'sysprompt') {
+ if (contentItem.type.endsWith('_preset') || ['instruct', 'context', 'sysprompt', 'reasoning'].includes(contentItem.type)) {
contentItem.name = path.parse(contentItem.filename).name;
contentItem.folder = getTargetByType(contentItem.type, directories);
presets.push(contentItem);
@@ -299,6 +300,8 @@ function getTargetByType(type, directories) {
return directories.quickreplies;
case CONTENT_TYPES.SYSPROMPT:
return directories.sysprompt;
+ case CONTENT_TYPES.REASONING:
+ return directories.reasoning;
default:
return null;
}
diff --git a/src/endpoints/presets.js b/src/endpoints/presets.js
index d742f6289..7c5db1fdb 100644
--- a/src/endpoints/presets.js
+++ b/src/endpoints/presets.js
@@ -30,6 +30,8 @@ function getPresetSettingsByAPI(apiId, directories) {
return { folder: directories.context, extension: '.json' };
case 'sysprompt':
return { folder: directories.sysprompt, extension: '.json' };
+ case 'reasoning':
+ return { folder: directories.reasoning, extension: '.json' };
default:
return { folder: null, extension: null };
}
diff --git a/src/endpoints/settings.js b/src/endpoints/settings.js
index 3b1209fdc..6c25e1f10 100644
--- a/src/endpoints/settings.js
+++ b/src/endpoints/settings.js
@@ -254,6 +254,7 @@ router.post('/get', (request, response) => {
const instruct = readAndParseFromDirectory(request.user.directories.instruct);
const context = readAndParseFromDirectory(request.user.directories.context);
const sysprompt = readAndParseFromDirectory(request.user.directories.sysprompt);
+ const reasoning = readAndParseFromDirectory(request.user.directories.reasoning);
response.send({
settings,
@@ -272,6 +273,7 @@ router.post('/get', (request, response) => {
instruct,
context,
sysprompt,
+ reasoning,
enable_extensions: ENABLE_EXTENSIONS,
enable_extensions_auto_update: ENABLE_EXTENSIONS_AUTO_UPDATE,
enable_accounts: ENABLE_ACCOUNTS,
diff --git a/src/users.js b/src/users.js
index 3a8c6c876..b02c81c73 100644
--- a/src/users.js
+++ b/src/users.js
@@ -95,6 +95,7 @@ const STORAGE_KEYS = {
* @property {string} vectors - The directory where the vectors are stored
* @property {string} backups - The directory where the backups are stored
* @property {string} sysprompt - The directory where the system prompt data is stored
+ * @property {string} reasoning - The directory where the reasoning templates are stored
*/
/**
From d31475254730c49bcda47a37c7ed715ac436052a Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sun, 16 Mar 2025 22:39:43 +0200
Subject: [PATCH 02/21] Add reasoning template to connection profiles
---
public/scripts/extensions/connection-manager/index.js | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/public/scripts/extensions/connection-manager/index.js b/public/scripts/extensions/connection-manager/index.js
index 2432df2b6..6909cd683 100644
--- a/public/scripts/extensions/connection-manager/index.js
+++ b/public/scripts/extensions/connection-manager/index.js
@@ -39,6 +39,7 @@ const CC_COMMANDS = [
'proxy',
'stop-strings',
'start-reply-with',
+ 'reasoning-template',
];
const TC_COMMANDS = [
@@ -54,6 +55,7 @@ const TC_COMMANDS = [
'tokenizer',
'stop-strings',
'start-reply-with',
+ 'reasoning-template',
];
const FANCY_NAMES = {
@@ -70,6 +72,7 @@ const FANCY_NAMES = {
'tokenizer': 'Tokenizer',
'stop-strings': 'Custom Stopping Strings',
'start-reply-with': 'Start Reply With',
+ 'reasoning-template': 'Reasoning Template',
};
/**
@@ -154,6 +157,7 @@ const profilesProvider = () => [
* @property {string} [tokenizer] Tokenizer
* @property {string} [stop-strings] Custom Stopping Strings
* @property {string} [start-reply-with] Start Reply With
+ * @property {string} [reasoning-template] Reasoning Template
* @property {string[]} [exclude] Commands to exclude
*/
From 0e41db615e54c2f0dd87e3366b44850a5071d184 Mon Sep 17 00:00:00 2001
From: bmen25124
Date: Sun, 16 Mar 2025 23:44:02 +0300
Subject: [PATCH 03/21] New exports
---
public/scripts/instruct-mode.js | 15 +++++++++------
public/scripts/openai.js | 12 +++++++-----
public/scripts/power-user.js | 14 ++++++++++----
public/scripts/st-context.js | 5 ++++-
public/scripts/world-info.js | 10 ++++++----
5 files changed, 36 insertions(+), 20 deletions(-)
diff --git a/public/scripts/instruct-mode.js b/public/scripts/instruct-mode.js
index 6ed4e9e27..b728a845f 100644
--- a/public/scripts/instruct-mode.js
+++ b/public/scripts/instruct-mode.js
@@ -398,23 +398,26 @@ export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvata
/**
* Formats instruct mode system prompt.
* @param {string} systemPrompt System prompt string.
+ * @param {InstructSettings} customInstruct Custom instruct mode settings.
* @returns {string} Formatted instruct mode system prompt.
*/
-export function formatInstructModeSystemPrompt(systemPrompt) {
+export function formatInstructModeSystemPrompt(systemPrompt, customInstruct = null) {
if (!systemPrompt) {
return '';
}
- const separator = power_user.instruct.wrap ? '\n' : '';
+ const instruct = customInstruct ?? power_user.instruct;
- if (power_user.instruct.system_sequence_prefix) {
+ const separator = instruct.wrap ? '\n' : '';
+
+ if (instruct.system_sequence_prefix) {
// TODO: Replace with a proper 'System' prompt entity name input
- const prefix = power_user.instruct.system_sequence_prefix.replace(/{{name}}/gi, 'System');
+ const prefix = instruct.system_sequence_prefix.replace(/{{name}}/gi, 'System');
systemPrompt = prefix + separator + systemPrompt;
}
- if (power_user.instruct.system_sequence_suffix) {
- systemPrompt = systemPrompt + separator + power_user.instruct.system_sequence_suffix;
+ if (instruct.system_sequence_suffix) {
+ systemPrompt = systemPrompt + separator + instruct.system_sequence_suffix;
}
return systemPrompt;
diff --git a/public/scripts/openai.js b/public/scripts/openai.js
index 112726562..45d5932e4 100644
--- a/public/scripts/openai.js
+++ b/public/scripts/openai.js
@@ -705,16 +705,18 @@ export function parseExampleIntoIndividual(messageExampleString, appendNamesForG
return result;
}
-function formatWorldInfo(value) {
+export function formatWorldInfo(value, { wiFormat = null } = {}) {
if (!value) {
return '';
}
- if (!oai_settings.wi_format.trim()) {
+ const format = wiFormat || oai_settings.wi_format;
+
+ if (!format.trim()) {
return value;
}
- return stringFormat(oai_settings.wi_format, value);
+ return stringFormat(format, value);
}
/**
@@ -952,7 +954,7 @@ async function populateDialogueExamples(prompts, chatCompletion, messageExamples
* @param {number} position - Prompt position in the extensions object.
* @returns {string|false} - The prompt position for prompt collection.
*/
-function getPromptPosition(position) {
+export function getPromptPosition(position) {
if (position == extension_prompt_types.BEFORE_PROMPT) {
return 'start';
}
@@ -969,7 +971,7 @@ function getPromptPosition(position) {
* @param {number} role Role of the prompt.
* @returns {string} Mapped role.
*/
-function getPromptRole(role) {
+export function getPromptRole(role) {
switch (role) {
case extension_prompt_roles.SYSTEM:
return 'system';
diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js
index 9f89bf6e2..f300d3200 100644
--- a/public/scripts/power-user.js
+++ b/public/scripts/power-user.js
@@ -1985,15 +1985,21 @@ export function fuzzySearchGroups(searchValue, fuzzySearchCaches = null) {
/**
* Renders a story string template with the given parameters.
* @param {object} params Template parameters.
+ * @param {object} [options] Additional options.
+ * @param {string} [options.customStoryString] Custom story string template.
+ * @param {InstructSettings} [options.customInstructSettings] Custom instruct settings.
* @returns {string} The rendered story string.
*/
-export function renderStoryString(params) {
+export function renderStoryString(params, { customStoryString = null, customInstructSettings = null } = {}) {
try {
+ const storyString = customStoryString ?? power_user.context.story_string;
+ const instructSettings = customInstructSettings ?? power_user.instruct;
+
// Validate and log possible warnings/errors
- validateStoryString(power_user.context.story_string, params);
+ validateStoryString(storyString, params);
// compile the story string template into a function, with no HTML escaping
- const compiledTemplate = Handlebars.compile(power_user.context.story_string, { noEscape: true });
+ const compiledTemplate = Handlebars.compile(storyString, { noEscape: true });
// render the story string template with the given params
let output = compiledTemplate(params);
@@ -2006,7 +2012,7 @@ export function renderStoryString(params) {
// add a newline to the end of the story string if it doesn't have one
if (output.length > 0 && !output.endsWith('\n')) {
- if (!power_user.instruct.enabled || power_user.instruct.wrap) {
+ if (!instructSettings.enabled || instructSettings.wrap) {
output += '\n';
}
}
diff --git a/public/scripts/st-context.js b/public/scripts/st-context.js
index 39799a9dd..5b0fa50e9 100644
--- a/public/scripts/st-context.js
+++ b/public/scripts/st-context.js
@@ -49,6 +49,7 @@ import {
clearChat,
unshallowCharacter,
deleteLastMessage,
+ getCharacterCardFields,
} from '../script.js';
import {
extension_settings,
@@ -78,7 +79,7 @@ import { ToolManager } from './tool-calling.js';
import { accountStorage } from './util/AccountStorage.js';
import { timestampToMoment, uuidv4 } from './utils.js';
import { getGlobalVariable, getLocalVariable, setGlobalVariable, setLocalVariable } from './variables.js';
-import { convertCharacterBook, loadWorldInfo, saveWorldInfo, updateWorldInfoList } from './world-info.js';
+import { convertCharacterBook, getWorldInfoPrompt, loadWorldInfo, saveWorldInfo, updateWorldInfoList } from './world-info.js';
import { ChatCompletionService, TextCompletionService } from './custom-request.js';
import { ConnectionManagerRequestService } from './extensions/shared.js';
import { updateReasoningUI, parseReasoningFromString } from './reasoning.js';
@@ -189,6 +190,7 @@ export function getContext() {
textCompletionSettings: textgenerationwebui_settings,
powerUserSettings: power_user,
getCharacters,
+ getCharacterCardFields,
uuidv4,
humanizedDateTime,
updateMessageBlock,
@@ -207,6 +209,7 @@ export function getContext() {
saveWorldInfo,
updateWorldInfoList,
convertCharacterBook,
+ getWorldInfoPrompt,
CONNECT_API_MAP,
getTextGenServer,
extractMessageFromData,
diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js
index a2dfb2dd4..8dc63b94d 100644
--- a/public/scripts/world-info.js
+++ b/public/scripts/world-info.js
@@ -756,7 +756,7 @@ export const worldInfoCache = new StructuredCloneMap({ cloneOnGet: true, cloneOn
* @param {string[]} chat The chat messages to scan, in reverse order.
* @param {number} maxContext The maximum context size of the generation.
* @param {boolean} isDryRun If true, the function will not emit any events.
- * @typedef {{worldInfoString: string, worldInfoBefore: string, worldInfoAfter: string, worldInfoExamples: any[], worldInfoDepth: any[]}} WIPromptResult
+ * @typedef {{worldInfoString: string, worldInfoBefore: string, worldInfoAfter: string, worldInfoExamples: any[], worldInfoDepth: any[], anBefore: any[], anAfter: any[]}} WIPromptResult
* @returns {Promise} The world info string and depth.
*/
export async function getWorldInfoPrompt(chat, maxContext, isDryRun) {
@@ -778,6 +778,8 @@ export async function getWorldInfoPrompt(chat, maxContext, isDryRun) {
worldInfoAfter,
worldInfoExamples: activatedWorldInfo.EMEntries ?? [],
worldInfoDepth: activatedWorldInfo.WIDepthEntries ?? [],
+ anBefore: activatedWorldInfo.ANBeforeEntries ?? [],
+ anAfter: activatedWorldInfo.ANAfterEntries ?? [],
};
}
@@ -3862,7 +3864,7 @@ function parseDecorators(content) {
* @param {string[]} chat The chat messages to scan, in reverse order.
* @param {number} maxContext The maximum context size of the generation.
* @param {boolean} isDryRun Whether to perform a dry run.
- * @typedef {{ worldInfoBefore: string, worldInfoAfter: string, EMEntries: any[], WIDepthEntries: any[], allActivatedEntries: Set }} WIActivated
+ * @typedef {{ worldInfoBefore: string, worldInfoAfter: string, EMEntries: any[], WIDepthEntries: any[], ANBeforeEntries: any[], ANAfterEntries: any[], allActivatedEntries: Set }} WIActivated
* @returns {Promise} The world info activated.
*/
export async function checkWorldInfo(chat, maxContext, isDryRun) {
@@ -3906,7 +3908,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
timedEffects.checkTimedEffects();
if (sortedEntries.length === 0) {
- return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], allActivatedEntries: new Set() };
+ return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], ANBeforeEntries: [], ANAfterEntries: [], allActivatedEntries: new Set() };
}
/** @type {number[]} Represents the delay levels for entries that are delayed until recursion */
@@ -4355,7 +4357,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
console.log(`[WI] ${isDryRun ? 'Hypothetically adding' : 'Adding'} ${allActivatedEntries.size} entries to prompt`, Array.from(allActivatedEntries.values()));
console.debug(`[WI] --- DONE${isDryRun ? ' (DRY RUN)' : ''} ---`);
- return { worldInfoBefore, worldInfoAfter, EMEntries, WIDepthEntries, allActivatedEntries: new Set(allActivatedEntries.values()) };
+ return { worldInfoBefore, worldInfoAfter, EMEntries, WIDepthEntries, ANBeforeEntries: ANTopEntries, ANAfterEntries: ANBottomEntries, allActivatedEntries: new Set(allActivatedEntries.values()) };
}
/**
From aacbc5b6db0252847e20040ee828354002e2bda0 Mon Sep 17 00:00:00 2001
From: Wolfsblvt
Date: Sun, 16 Mar 2025 21:48:50 +0100
Subject: [PATCH 04/21] Fix PR workflow by chaining actions
---
.github/workflows/pr-auto-manager.yml | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/pr-auto-manager.yml b/.github/workflows/pr-auto-manager.yml
index 01656c1cd..e2dd9bb43 100644
--- a/.github/workflows/pr-auto-manager.yml
+++ b/.github/workflows/pr-auto-manager.yml
@@ -39,9 +39,13 @@ jobs:
label-by-branches:
name: 🏷️ Label PR by Branches
+ needs: [label-by-size]
runs-on: ubuntu-latest
+ # Run, even if the previous jobs were skipped/failed
# Only label once when PR is created or branches are changed, to allow manual label removal
- if: github.event.action == 'opened' || (github.event.action == 'synchronize' && (github.event.changes.base || github.event.changes.head))
+ if: |
+ always()
+ && github.event.action == 'opened' || (github.event.action == 'synchronize' && (github.event.changes.base || github.event.changes.head))
steps:
- name: Checkout Repository
@@ -59,7 +63,10 @@ jobs:
label-by-files:
name: 🏷️ Label PR by Files
+ needs: [label-by-branches]
runs-on: ubuntu-latest
+ # Run, even if the previous jobs were skipped/failed
+ if: always()
steps:
- name: Checkout Repository
@@ -77,9 +84,12 @@ jobs:
remove-stale-label:
name: 🗑️ Remove Stale Label on Comment
+ needs: [label-by-files]
runs-on: ubuntu-latest
# Only runs when this is not done by the github actions bot
- if: github.event_name == 'pull_request_review_comment' && github.actor != 'github-actions[bot]'
+ if: |
+ always()
+ && github.event_name == 'pull_request_review_comment' && github.actor != 'github-actions[bot]'
steps:
- name: Remove Stale Label
@@ -94,7 +104,7 @@ jobs:
check-merge-blocking-labels:
name: 🚫 Check Merge Blocking Labels
- needs: [label-by-branches, label-by-files]
+ needs: [label-by-size, label-by-branches, label-by-files, remove-stale-label]
runs-on: ubuntu-latest
# Run, even if the previous jobs were skipped/failed
if: always()
@@ -144,7 +154,7 @@ jobs:
write-auto-comments:
name: 💬 Post PR Comments Based on Labels
- needs: [label-by-size, label-by-branches, label-by-files]
+ needs: [label-by-size, label-by-branches, label-by-files, remove-stale-label]
runs-on: ubuntu-latest
# Run, even if the previous jobs were skipped/failed
if: always()
From 1593951281bc2ccbab85b8e00d7223d4650d7580 Mon Sep 17 00:00:00 2001
From: bmen25124
Date: Mon, 17 Mar 2025 00:00:37 +0300
Subject: [PATCH 05/21] Cloned preset before using
---
public/scripts/instruct-mode.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/public/scripts/instruct-mode.js b/public/scripts/instruct-mode.js
index b728a845f..000d242b0 100644
--- a/public/scripts/instruct-mode.js
+++ b/public/scripts/instruct-mode.js
@@ -406,7 +406,7 @@ export function formatInstructModeSystemPrompt(systemPrompt, customInstruct = nu
return '';
}
- const instruct = customInstruct ?? power_user.instruct;
+ const instruct = structuredClone(customInstruct ?? power_user.instruct);
const separator = instruct.wrap ? '\n' : '';
From d887eb2258ade5558cab51ea8001b85716510bf8 Mon Sep 17 00:00:00 2001
From: Wolfsblvt
Date: Sun, 16 Mar 2025 22:08:22 +0100
Subject: [PATCH 06/21] Revert "Fix PR workflow by chaining actions"
This reverts commit aacbc5b6db0252847e20040ee828354002e2bda0.
---
.github/workflows/pr-auto-manager.yml | 18 ++++--------------
1 file changed, 4 insertions(+), 14 deletions(-)
diff --git a/.github/workflows/pr-auto-manager.yml b/.github/workflows/pr-auto-manager.yml
index e2dd9bb43..01656c1cd 100644
--- a/.github/workflows/pr-auto-manager.yml
+++ b/.github/workflows/pr-auto-manager.yml
@@ -39,13 +39,9 @@ jobs:
label-by-branches:
name: 🏷️ Label PR by Branches
- needs: [label-by-size]
runs-on: ubuntu-latest
- # Run, even if the previous jobs were skipped/failed
# Only label once when PR is created or branches are changed, to allow manual label removal
- if: |
- always()
- && github.event.action == 'opened' || (github.event.action == 'synchronize' && (github.event.changes.base || github.event.changes.head))
+ if: github.event.action == 'opened' || (github.event.action == 'synchronize' && (github.event.changes.base || github.event.changes.head))
steps:
- name: Checkout Repository
@@ -63,10 +59,7 @@ jobs:
label-by-files:
name: 🏷️ Label PR by Files
- needs: [label-by-branches]
runs-on: ubuntu-latest
- # Run, even if the previous jobs were skipped/failed
- if: always()
steps:
- name: Checkout Repository
@@ -84,12 +77,9 @@ jobs:
remove-stale-label:
name: 🗑️ Remove Stale Label on Comment
- needs: [label-by-files]
runs-on: ubuntu-latest
# Only runs when this is not done by the github actions bot
- if: |
- always()
- && github.event_name == 'pull_request_review_comment' && github.actor != 'github-actions[bot]'
+ if: github.event_name == 'pull_request_review_comment' && github.actor != 'github-actions[bot]'
steps:
- name: Remove Stale Label
@@ -104,7 +94,7 @@ jobs:
check-merge-blocking-labels:
name: 🚫 Check Merge Blocking Labels
- needs: [label-by-size, label-by-branches, label-by-files, remove-stale-label]
+ needs: [label-by-branches, label-by-files]
runs-on: ubuntu-latest
# Run, even if the previous jobs were skipped/failed
if: always()
@@ -154,7 +144,7 @@ jobs:
write-auto-comments:
name: 💬 Post PR Comments Based on Labels
- needs: [label-by-size, label-by-branches, label-by-files, remove-stale-label]
+ needs: [label-by-size, label-by-branches, label-by-files]
runs-on: ubuntu-latest
# Run, even if the previous jobs were skipped/failed
if: always()
From 111fa0dee5e7feaa4750e25e08a2af0b1be878bb Mon Sep 17 00:00:00 2001
From: Wolfsblvt
Date: Sun, 16 Mar 2025 22:09:28 +0100
Subject: [PATCH 07/21] Use older version of PR size labeler
---
.github/workflows/pr-auto-manager.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/pr-auto-manager.yml b/.github/workflows/pr-auto-manager.yml
index 01656c1cd..52e061840 100644
--- a/.github/workflows/pr-auto-manager.yml
+++ b/.github/workflows/pr-auto-manager.yml
@@ -19,7 +19,7 @@ jobs:
- name: Label PR Size
# Pull Request Size Labeler
# https://github.com/marketplace/actions/pull-request-size-labeler
- uses: codelytv/pr-size-labeler@v1.10.2
+ uses: codelytv/pr-size-labeler@v1.10.1
with:
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
xs_label: '🟩 ⬤○○○○'
From 8dc66bd21b1697ddddc7709ac733b6c8a81e2081 Mon Sep 17 00:00:00 2001
From: bmen25124
Date: Mon, 17 Mar 2025 00:13:39 +0300
Subject: [PATCH 08/21] Better WI type readability, fixed clone and type
opeators.
---
public/scripts/openai.js | 2 +-
public/scripts/power-user.js | 2 +-
public/scripts/world-info.js | 24 +++++++++++++++++++-----
3 files changed, 21 insertions(+), 7 deletions(-)
diff --git a/public/scripts/openai.js b/public/scripts/openai.js
index 45d5932e4..098576da9 100644
--- a/public/scripts/openai.js
+++ b/public/scripts/openai.js
@@ -710,7 +710,7 @@ export function formatWorldInfo(value, { wiFormat = null } = {}) {
return '';
}
- const format = wiFormat || oai_settings.wi_format;
+ const format = wiFormat ?? oai_settings.wi_format;
if (!format.trim()) {
return value;
diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js
index f300d3200..00ce80e8d 100644
--- a/public/scripts/power-user.js
+++ b/public/scripts/power-user.js
@@ -1992,7 +1992,7 @@ export function fuzzySearchGroups(searchValue, fuzzySearchCaches = null) {
*/
export function renderStoryString(params, { customStoryString = null, customInstructSettings = null } = {}) {
try {
- const storyString = customStoryString ?? power_user.context.story_string;
+ const storyString = structuredClone(customStoryString ?? power_user.context.story_string);
const instructSettings = customInstructSettings ?? power_user.instruct;
// Validate and log possible warnings/errors
diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js
index 8dc63b94d..bda24ed32 100644
--- a/public/scripts/world-info.js
+++ b/public/scripts/world-info.js
@@ -753,10 +753,17 @@ export const worldInfoCache = new StructuredCloneMap({ cloneOnGet: true, cloneOn
/**
* Gets the world info based on chat messages.
- * @param {string[]} chat The chat messages to scan, in reverse order.
- * @param {number} maxContext The maximum context size of the generation.
- * @param {boolean} isDryRun If true, the function will not emit any events.
- * @typedef {{worldInfoString: string, worldInfoBefore: string, worldInfoAfter: string, worldInfoExamples: any[], worldInfoDepth: any[], anBefore: any[], anAfter: any[]}} WIPromptResult
+ * @param {string[]} chat - The chat messages to scan, in reverse order.
+ * @param {number} maxContext - The maximum context size of the generation.
+ * @param {boolean} isDryRun - If true, the function will not emit any events.
+ * @typedef {object} WIPromptResult
+ * @property {string} worldInfoString - Complete world info string
+ * @property {string} worldInfoBefore - World info that goes before the prompt
+ * @property {string} worldInfoAfter - World info that goes after the prompt
+ * @property {Array} worldInfoExamples - Array of example entries
+ * @property {Array} worldInfoDepth - Array of depth entries
+ * @property {Array} anBefore - Array of entries before Author's Note
+ * @property {Array} anAfter - Array of entries after Author's Note
* @returns {Promise} The world info string and depth.
*/
export async function getWorldInfoPrompt(chat, maxContext, isDryRun) {
@@ -3864,7 +3871,14 @@ function parseDecorators(content) {
* @param {string[]} chat The chat messages to scan, in reverse order.
* @param {number} maxContext The maximum context size of the generation.
* @param {boolean} isDryRun Whether to perform a dry run.
- * @typedef {{ worldInfoBefore: string, worldInfoAfter: string, EMEntries: any[], WIDepthEntries: any[], ANBeforeEntries: any[], ANAfterEntries: any[], allActivatedEntries: Set }} WIActivated
+ * @typedef {object} WIActivated
+ * @property {string} worldInfoBefore The world info before the chat.
+ * @property {string} worldInfoAfter The world info after the chat.
+ * @property {any[]} EMEntries The entries for examples.
+ * @property {any[]} WIDepthEntries The depth entries.
+ * @property {any[]} ANBeforeEntries The entries before Author's Note.
+ * @property {any[]} ANAfterEntries The entries after Author's Note.
+ * @property {Set} allActivatedEntries All entries.
* @returns {Promise} The world info activated.
*/
export async function checkWorldInfo(chat, maxContext, isDryRun) {
From 0bdb131c2299064ba930a83f1bcdb4072dfc8053 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sun, 16 Mar 2025 23:25:42 +0200
Subject: [PATCH 09/21] Refine Docker CLI documentation
---
.github/readme.md | 37 ++++++++++++++++++++++++++-----------
1 file changed, 26 insertions(+), 11 deletions(-)
diff --git a/.github/readme.md b/.github/readme.md
index 2a20afb08..808b76bd0 100644
--- a/.github/readme.md
+++ b/.github/readme.md
@@ -192,28 +192,43 @@ You will need two mandatory directory mappings and a port mapping to allow Silly
##### Volume Mappings
-* [config] - The directory where SillyTavern configuration files will be stored on your host machine
-* [data] - The directory where SillyTavern user data (including characters) will be stored on your host machine
-* [plugins] - (optional) The directory where SillyTavern server plugins will be stored on your host machine
-* [extensions] - (optional) The directory where global UI extensions will be stored on your host machine
+* `CONFIG_PATH` - The directory where SillyTavern configuration files will be stored on your host machine
+* `DATA_PATH` - The directory where SillyTavern user data (including characters) will be stored on your host machine
+* `PLUGINS_PATH` - (optional) The directory where SillyTavern server plugins will be stored on your host machine
+* `EXTENSIONS_PATH` - (optional) The directory where global UI extensions will be stored on your host machine
##### Port Mappings
-* [PublicPort] - The port to expose the traffic on. This is mandatory, as you will be accessing the instance from outside of its virtual machine container. DO NOT expose this to the internet without implementing a separate service for security.
+* `PUBLIC_PORT` - The port to expose the traffic on. This is mandatory, as you will be accessing the instance from outside of its virtual machine container. DO NOT expose this to the internet without implementing a separate service for security.
##### Additional Settings
-* [DockerNet] - The docker network that the container should be created with a connection to. If you don't know what it is, see the [official Docker documentation](https://docs.docker.com/reference/cli/docker/network/).
-* [version] - On the right-hand side of this GitHub page, you'll see "Packages". Select the "sillytavern" package and you'll see the image versions. The image tag "latest" will keep you up-to-date with the current release. You can also utilize "staging" and "release" tags that point to the nightly images of the respective branches, but this may not be appropriate, if you are utilizing extensions that could be broken, and may need time to update.
+* `SILLYTAVERN_VERSION` - On the right-hand side of this GitHub page, you'll see "Packages". Select the "sillytavern" package and you'll see the image versions. The image tag "latest" will keep you up-to-date with the current release. You can also utilize "staging" that points to the nightly image the respective branch.
-#### Install command
+#### Running the container
1. Open your Command Line
-2. Run the following command
+2. Run the following command in a folder where you want to store the configuration and data files:
-`docker run --name='sillytavern' --net='[DockerNet]' -p '8000:8000/tcp' -v '[plugins]':'/home/node/app/plugins':'rw' -v '[config]':'/home/node/app/config':'rw' -v '[data]':'/home/node/app/data':'rw' -v '[extensions]':'/home/node/app/public/scripts/extensions/third-party':'rw' 'ghcr.io/sillytavern/sillytavern:[version]'`
+```bash
+SILLYTAVERN_VERSION="latest"
+PUBLIC_PORT="8000"
+CONFIG_PATH="./config"
+DATA_PATH="./data"
+PLUGINS_PATH="./plugins"
+EXTENSIONS_PATH="./extensions"
-> Note that 8000 is a default listening port. Don't forget to use an appropriate port if you change it in the config.
+docker run \
+ --name="sillytavern" \
+ -p "$PUBLIC_PORT:8000/tcp" \
+ -v "$CONFIG_PATH:/home/node/app/config:rw" \
+ -v "$DATA_PATH:/home/node/app/data:rw" \
+ -v "$EXTENSIONS_PATH:/home/node/app/public/scripts/extensions/third-party:rw" \
+ -v "$PLUGINS_PATH:/home/node/app/plugins:rw" \
+ ghcr.io/sillytavern/sillytavern:"$SILLYTAVERN_VERSION"
+```
+
+> By default the container will run in the foreground. If you want to run it in the background, add the `-d` flag to the `docker run` command.
### Building the image yourself
From c022858e5b667f121045b1b6d2873ddc40ac5381 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sun, 16 Mar 2025 23:37:18 +0200
Subject: [PATCH 10/21] Fix structuredClone
---
public/scripts/power-user.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js
index 00ce80e8d..c1cc8c0f8 100644
--- a/public/scripts/power-user.js
+++ b/public/scripts/power-user.js
@@ -1992,8 +1992,8 @@ export function fuzzySearchGroups(searchValue, fuzzySearchCaches = null) {
*/
export function renderStoryString(params, { customStoryString = null, customInstructSettings = null } = {}) {
try {
- const storyString = structuredClone(customStoryString ?? power_user.context.story_string);
- const instructSettings = customInstructSettings ?? power_user.instruct;
+ const storyString = customStoryString ?? power_user.context.story_string;
+ const instructSettings = structuredClone(customInstructSettings ?? power_user.instruct);
// Validate and log possible warnings/errors
validateStoryString(storyString, params);
From 40c3674da1c1b5220e39d95ce1c630e93dda4bf5 Mon Sep 17 00:00:00 2001
From: Yokayo
Date: Mon, 17 Mar 2025 16:09:36 +0700
Subject: [PATCH 11/21] Update tl
---
public/index.html | 6 +-
public/locales/ru-ru.json | 159 +++++++++++++++---
public/script.js | 4 +-
public/scripts/backgrounds.js | 3 +-
public/scripts/bookmarks.js | 2 +-
public/scripts/extensions/assets/index.js | 2 +-
.../extensions/attachments/attach-button.html | 2 +-
public/scripts/extensions/regex/editor.html | 4 +-
public/scripts/extensions/regex/index.js | 2 +-
public/scripts/group-chats.js | 2 +-
public/scripts/horde.js | 7 +-
public/scripts/personas.js | 2 +-
public/scripts/samplerSelect.js | 21 +--
public/scripts/secrets.js | 3 +-
public/scripts/templates/assistantNote.html | 4 +-
public/scripts/templates/samplerSelector.html | 18 ++
16 files changed, 181 insertions(+), 60 deletions(-)
create mode 100644 public/scripts/templates/samplerSelector.html
diff --git a/public/index.html b/public/index.html
index 0753e3134..500dcf469 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1957,7 +1957,7 @@
Enable web search
-
+
Use search capabilities provided by the backend.
@@ -2188,7 +2188,7 @@
Trusted workers only
- Context: --, Response: --
+ Context : --, Response : --
API key
Get it here: Register (View my Kudos )
@@ -6563,7 +6563,7 @@
-
in this group
+
in this group
diff --git a/public/locales/ru-ru.json b/public/locales/ru-ru.json
index 4efd0d03e..6960cba3d 100644
--- a/public/locales/ru-ru.json
+++ b/public/locales/ru-ru.json
@@ -23,9 +23,8 @@
"Mirostat Mode": "Режим",
"Mirostat Tau": "Tau",
"Mirostat Eta": "Eta",
- "Variability parameter for Mirostat outputs": "Параметр изменчивости для выходных данных Mirostat.",
+ "Variability parameter for Mirostat outputs": "Вариативность для выходных данных Mirostat.",
"Learning rate of Mirostat": "Скорость обучения Mirostat.",
- "Strength of the Contrastive Search regularization term. Set to 0 to disable CS": "Сила условия регуляризации контрастивного поиска. Установите значение 0, чтобы отключить CS.",
"Temperature Last": "Температура последней",
"LLaMA / Mistral / Yi models only": "Только для моделей LLaMA / Mistral / Yi. Перед этим обязательно выберите подходящий токенизатор.\nПоследовательности, которых не должно быть на выходе.\nОдна на строку. Текст или [идентификаторы токенов].\nМногие токены имеют пробел впереди. Используйте счетчик токенов, если не уверены.",
"Example: some text [42, 69, 1337]": "Пример:\nкакой-то текст\n[42, 69, 1337]",
@@ -60,13 +59,11 @@
"Add BOS Token": "Добавлять BOS-токен",
"Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative": "Добавлять BOS-токен в начале промпта. Если выключить, ответы могут стать более креативными.",
"Ban EOS Token": "Запретить EOS-токен",
- "Ban the eos_token. This forces the model to never end the generation prematurely": "Запрет EOS-токена не позволит модели завершить генерацию преждевременно",
+ "Ban the eos_token. This forces the model to never end the generation prematurely": "Запрет EOS-токена не позволит модели завершить генерацию самостоятельно (только при достижении лимита токенов)",
"Skip Special Tokens": "Пропускать спец. токены",
- "Beam search": "Поиск Beam",
- "Number of Beams": "Количество Beam",
+ "Beam search": "Beam Search",
"Length Penalty": "Штраф за длину",
- "Early Stopping": "Преждевременная остановка",
- "Contrastive search": "Контрастный поиск",
+ "Early Stopping": "Прекращать сразу",
"Penalty Alpha": "Penalty Alpha",
"Seed": "Зерно",
"Epsilon Cutoff": "Epsilon Cutoff",
@@ -89,7 +86,7 @@
"Text Completion presets": "Пресеты для Text Completion",
"Documentation on sampling parameters": "Документация по параметрам сэмплеров",
"Set all samplers to their neutral/disabled state.": "Установить все сэмплеры в нейтральное/отключенное состояние.",
- "Only enable this if your model supports context sizes greater than 8192 tokens": "Включайте эту опцию, только если ваша модель поддерживает размер контекста более 8192 токенов.\nУвеличивайте только если вы знаете, что делаете.",
+ "Only enable this if your model supports context sizes greater than 8192 tokens": "Включайте эту опцию, только если ваша модель поддерживает размер контекста более 8192 токенов.\nУвеличивайте только если вы понимаете, что делаете.",
"Wrap in Quotes": "Заключать в кавычки",
"Wrap entire user message in quotes before sending.": "Перед отправкой заключать всё сообщение пользователя в кавычки.",
"Leave off if you use quotes manually for speech.": "Оставьте выключенным, если вручную выставляете кавычки для прямой речи.",
@@ -109,7 +106,7 @@
"Adjust response length to worker capabilities": "Подстраивать длину ответа под возможности рабочих машин",
"API key": "API-ключ",
"Tabby API key": "Tabby API-ключ",
- "Get it here:": "Получить здесь:",
+ "Get it here:": "Получите здесь:",
"Register": "Зарегистрироваться",
"TogetherAI Model": "Модель TogetherAI",
"Example: 127.0.0.1:5001": "Пример: http://127.0.0.1:5001",
@@ -289,10 +286,10 @@
"Author's Note": "Заметки автора",
"Replace empty message": "Заменять пустые сообщения",
"Send this text instead of nothing when the text box is empty.": "Этот текст будет отправлен в случае отсутствия текста на отправку.",
- "Unrestricted maximum value for the context slider": "Убрать потолок для ползунка контекста. Включайте только если точно знаете, что делаете",
+ "Unrestricted maximum value for the context slider": "Убрать потолок для ползунка контекста. Включайте только если точно понимаете, что делаете",
"Chat Completion Source": "Источник для Chat Completion",
- "Avoid sending sensitive information to the Horde.": "Избегайте отправки личной информации Horde",
- "Review the Privacy statement": "Ознакомиться с заявлением о конфиденциальности",
+ "Avoid sending sensitive information to the Horde.": "Избегайте отправки личной информации Horde.",
+ "Review the Privacy statement": "Ознакомьтесь с заявлением о конфиденциальности",
"Trusted workers only": "Только доверенные рабочие машины",
"For privacy reasons, your API key will be hidden after you reload the page.": "Из соображений безопасности ваш API-ключ будет скрыт после перезагрузки страницы.",
"-- Horde models not loaded --": "--Модель Horde не загружена--",
@@ -699,7 +696,7 @@
"Aggressive": "Агрессивный",
"Very aggressive": "Очень агрессивный",
"Eta_Cutoff_desc": "Eta cutoff - основной параметр специальной техники сэмплинга под названием Eta Sampling.
В единицах 1e-4; разумное значение - 3.
Установите в 0, чтобы отключить.
См. статью Truncation Sampling as Language Model Desmoothing от Хьюитт и др. (2022) для получения подробной информации.",
- "Learn how to contribute your idle GPU cycles to the Horde": "Узнайте, как внести свой вклад в свои свободные GPU-циклы в орду",
+ "Learn how to contribute your idle GPU cycles to the Horde": "Узнайте, как использовать время простоя вашего GPU для помощи Horde",
"Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Используйте соответствующий токенизатор для моделей Google через их API. Медленная обработка подсказок, но предлагает намного более точный подсчет токенов.",
"Load koboldcpp order": "Загрузить порядок из koboldcpp",
"Use Google Tokenizer": "Использовать токенизатор Google",
@@ -744,7 +741,7 @@
"Last Assistant Prefix": "Последний префикс ассистента",
"System Instruction Prefix": "Префикс системной инструкции",
"User Filler Message": "Принудительное сообщение пользователя",
- "Permanent": "перманентных",
+ "Permanent": "постоянных",
"Alt. Greetings": "Др. варианты",
"Smooth Streaming": "Плавный стриминг",
"Save checkpoint": "Сохранить чекпоинт",
@@ -1227,7 +1224,6 @@
"JSON-serialized array of strings.": "Список строк в формате JSON.",
"Mirostat_desc": "Mirostat - своего рода термометр, измеряющий перплексию для выводимого текста.\nMirostat подгоняет перплексию генерируемого текста к перплексии входного текста, что позволяет избежать повторов.\n(когда по мере генерации текста авторегрессионным инференсом, перплексия всё больше приближается к нулю)\n а также ловушки перплексии (когда перплексия начинает уходить в сторону)\nБолее подробное описание в статье Mirostat: A Neural Text Decoding Algorithm that Directly Controls Perplexity by Basu et al. (2020).\nРежим выбирает версию Mirostat. 0=отключить, 1=Mirostat 1.0 (только llama.cpp), 2=Mirostat 2.0.",
"Helpful tip coming soon.": "Подсказку скоро добавим.",
- "Temperature_Last_desc": "Использовать Temperature сэмплер в последнюю очередь. Это почти всегда разумно.\nПри включении: сначала выборка набора правдоподобных токенов, затем применение Temperature для корректировки их относительных вероятностей (технически, логитов).\nПри отключении: сначала применение Temperature для корректировки относительных вероятностей ВСЕХ токенов, затем выборка правдоподобных токенов из этого.\nОтключение Temperature Last увеличивает вероятности в хвосте распределения, что увеличивает шансы получить несогласованный ответ.",
"Speculative Ngram": "Speculative Ngram",
"Use a different speculative decoding method without a draft model": "Use a different speculative decoding method without a draft model.\rUsing a draft model is preferred. Speculative ngram is not as effective.",
"Spaces Between Special Tokens": "Spaces Between Special Tokens",
@@ -1734,7 +1730,7 @@
"markdown_hotkeys_desc": "Включить горячие клавиши для вставки символов разметки в некоторых полях ввода. См. '/help hotkeys'.",
"Save and Update": "Сохранить и обновить",
"Profile name:": "Название профиля:",
- "API returned an error": "API вернуло ошибку",
+ "API returned an error": "API ответило ошибкой",
"Failed to save preset": "Не удалось сохранить пресет",
"Preset name should be unique.": "Название пресета должно быть уникальным.",
"Invalid file": "Невалидный файл",
@@ -1756,8 +1752,7 @@
"dot quota_error": "имеется достаточно кредитов.",
"If you have sufficient credits, please try again later.": "Если кредитов достаточно, то повторите попытку позднее.",
"Proxy preset '${0}' not found": "Пресет '${0}' не найден",
- "Window.ai returned an error": "Window.ai вернул ошибку",
- "Get it here:": "Загрузите здесь:",
+ "Window.ai returned an error": "Window.ai ответил ошибкой",
"Extension is not installed": "Расширение не установлено",
"Update or remove your reverse proxy settings.": "Измените или удалите ваши настройки прокси.",
"An error occurred while importing prompts. More info available in console.": "В процессе импорта произошла ошибка. Подробную информацию см. в консоли.",
@@ -1866,7 +1861,7 @@
"Group Chat could not be saved": "Не удалось сохранить групповой чат",
"Deleted group member swiped. To get a reply, add them back to the group.": "Вы пытаетесь свайпнуть удалённого члена группы. Чтобы получить ответ, добавьте этого персонажа обратно в группу.",
"Currently no group selected.": "В данный момент не выбрано ни одной группы.",
- "Not so fast! Wait for the characters to stop typing before deleting the group.": "Чуть помедленнее! Перед удалением группы дождитесь, пока персонаж закончит печатать.",
+ "Not so fast! Wait for the characters to stop typing before deleting the group.": "Чуть помедленнее! Перед удалением группы дождитесь, пока персонажи закончат печатать.",
"Delete the group?": "Удалить группу?",
"This will also delete all your chats with that group. If you want to delete a single conversation, select a \"View past chats\" option in the lower left menu.": "Вместе с ней будут удалены и все её чаты. Если требуется удалить только один чат, воспользуйтесь кнопкой \"Все чаты\" в меню в левом нижнем углу.",
"Can't peek a character while group reply is being generated": "Невозможно открыть карточку персонажа во время генерации ответа",
@@ -1997,7 +1992,7 @@
"Default persona deleted": "Удалена персона по умолчанию",
"The locked persona was deleted. You will need to set a new persona for this chat.": "Удалена привязанная к чату персона. Вам будет необходимо выбрать новую фиксированную персону для этого чата.",
"Persona deleted": "Персона удалена",
- "You must bind a name to this persona before you can set it as the default.": "Прежде чем установить эту персону в качестве персоны по умолчанию, ей необходимо задать имя.",
+ "You must bind a name to this persona before you can set it as the default.": "Прежде чем установить эту персону в качестве персоны по умолчанию, ей необходимо присвоить имя.",
"Persona name not set": "У персоны отсутствует имя",
"Are you sure you want to remove the default persona?": "Вы точно хотите снять статус персоны по умолчанию?",
"This persona will no longer be used by default when you open a new chat.": "Эта персона больше не будет автоматически выбираться при старте нового чата",
@@ -2203,5 +2198,127 @@
"Input:": "Входные данные:",
"Tokenized text:": "Токенизированный текст:",
"Token IDs:": "Идентификаторы токенов:",
- "Tokens:": "Токенов:"
+ "Tokens:": "Токенов:",
+ "Max prompt cost:": "Макс. стоимость промпта:",
+ "Reset custom sampler selection": "Сбросить подборку семплеров",
+ "Here you can toggle the display of individual samplers. (WIP)": "Здесь можно включить или выключить отображение каждого из сэмплеров отдельно. (WIP)",
+ "Request Model Reasoning": "Запрашивать цепочку рассуждений",
+ "Reasoning": "Рассуждения / Reasoning",
+ "Auto-Parse": "Авто-парсинг",
+ "reasoning_auto_parse": "Автоматически считывать блоки рассуждений, расположенные между префиксом и суффиксом рассуждений. Для работы должно быть указано и то, и другое.",
+ "Auto-Expand": "Разворачивать",
+ "reasoning_auto_expand": "Автоматически разворачивать блоки рассуждений.",
+ "Show Hidden": "Показывать время",
+ "reasoning_show_hidden": "Отображать затраченное на рассуждения время для моделей со скрытой цепочкой рассуждений",
+ "Add to Prompts": "Добавлять в промпт",
+ "reasoning_add_to_prompts": "Добавлять существующие блоки рассуждений в промпт. Для добавления новых используйте меню редактирования сообщений.",
+ "reasoning_max_additions": "Макс. кол-во блоков рассуждений в промпте, считается от последнего сообщения",
+ "Max": "Макс.",
+ "Reasoning Formatting": "Форматирование рассуждений",
+ "Prefix": "Префикс",
+ "Suffix": "Постфикс",
+ "Separator": "Разделитель",
+ "reasoning_separator": "Вставляется между рассуждениями и содержанием самого сообщения.",
+ "reasoning_prefix": "Вставляется перед рассуждениями.",
+ "reasoning_suffix": "Вставляется после рассуждений.",
+ "Seed_desc": "Фиксированное значение зерна позволяет получать предсказуемые, одинаковые результаты на одинаковых настройках. Поставьте -1 для рандомного зерна.",
+ "# of Beams": "Кол-во лучей",
+ "The number of sequences generated at each step with Beam Search.": "Кол-во вариантов, генерируемых Beam Search на каждом шаге работы.",
+ "Penalize sequences based on their length.": "Штрафует строки в зависимости от длины",
+ "Controls the stopping condition for beam search. If checked, the generation stops as soon as there are '# of Beams' sequences. If not checked, a heuristic is applied and the generation is stopped when it's very unlikely to find better candidates.": "Определяет, когда останавливать работу Beam Search. Поставив галочку, вы укажете поиску остановиться тогда, когда будет достигнуто кол-во лучей из соответствующего поля. Если галочку не отмечать, то генерация остановится тогда, когда сочтёт, что дальше найти лучших кандидатов слишком маловероятно.",
+ "A greedy, brute-force algorithm used in LLM sampling to find the most likely sequence of words or tokens. It expands multiple candidate sequences at once, maintaining a fixed number (beam width) of top sequences at each step.": "Жадный алгоритм LLM-сэмплинга, подбирающий наиболее вероятную последовательность слов или токенов путём исследования и расширения сразу нескольких вариантов. На каждом шаге он удерживает фиксированное кол-во самых подходящих вариантов (ширина луча).",
+ "Smooth_Sampling_desc": "Изменяет распределение с помощью квадратичных и кубических преобразований. Снижение Коэффициента сглаживания даёт более креативные ответы, обычно идеальное значение находится в диапазоне 0.2-0.3 (при кривой сглаживания=1.0). Повышение значения Кривой сглаживания сделает кривую круче, что приведёт к более агрессивной фильтрации маловероятных вариантов. Установив Кривую сглаживания = 1.0, вы фактически нейтрализуете этот параметр и будете работать только с Коэффициентом",
+ "Temperature_Last_desc": "Применять сэмплер Температуры в последнюю очередь. Почти всегда оправдано.\nПри включении: сначала все токены семплируются, и затем температура регулирует распределение у оставшихся (технически, у оставшихся логитов).\nПри выключении: сначала температура настраивает распределение ВСЕХ токенов, и потом они семплируются уже с этим обновлённым распределением.\nПри отключении этой опции токены в хвосте получают больше шансов попасть в итоговую последовательность, что может привести к менее связным и логичным ответам.",
+ "Swipe # for All Messages": "Номер свайпа на всех сообщениях",
+ "Display swipe numbers for all messages, not just the last.": "Отображать номер свайпа для всех сообщений, а не только для последнего.",
+ "Penalty Range": "Окно для штрафа",
+ "Never": "Никогда",
+ "Groups and Past Personas": "Для групп и прошлых персон",
+ "Always": "Всегда",
+ "Request model reasoning": "Запрашивать рассуждения",
+ "Allows the model to return its thinking process.": "Позволяет модели высылать в ответе свою цепочку рассуждений.",
+ "Rename Persona": "Переименовать персону",
+ "Change Persona Image": "Изменить изображение персоны",
+ "Duplicate Persona": "Клонировать персону",
+ "Delete Persona": "Удалить персону",
+ "Enter a new name for this persona:": "Введите новое имя персоны:",
+ "Connections": "Связи",
+ "Click to select this as default persona for the new chats. Click again to remove it.": "Нажмите, чтобы установить эту персону стандартной для всех новых чатов. Нажмите ещё раз, чтобы отключить.",
+ "Character": "Персонаж",
+ "Click to lock your selected persona to the current character. Click again to remove the lock.": "Нажмите, чтобы закрепить эту персону для текущего персонажа. Нажмите ещё раз, чтобы открепить.",
+ "Chat": "Чат",
+ "[No character connections. Click one of the buttons above to connect this persona.]": "[Связи отсутствуют. Нажмите на одну из кнопок выше, чтобы создать.]",
+ "Global Settings": "Общие настройки",
+ "Allow multiple persona connections per character": "Разрешить привязывать несколько персон к одному персонажу",
+ "When multiple personas are connected to a character, a popup will appear to select which one to use": "При связывании нескольких персон с персонажем, будет появляться окошко с предложением выбрать нужную.",
+ "Auto-lock a chosen persona to the chat": "Автоматически привязывать выбранную персону к чату",
+ "Whenever a persona is selected, it will be locked to the current chat and automatically selected when the chat is opened.": "При выборе новой персоны она автоматически будет привязана к текущему чату, и будет выбираться при его открытии.",
+ "Current Persona": "Текущая персона",
+ "The chat has been successfully converted!": "Чат успешно преобразован!",
+ "Manual": "Когда вы скажете",
+ "Auto Mode delay": "Задержка авто-режима",
+ "Use tag as folder": "Тег-папка",
+ "All connections to ${0} have been removed.": "Все связи с персонажем ${0} были удалены.",
+ "Personas Unlocked": "Персоны отвязаны",
+ "Remove All Connections": "Удалить все связи",
+ "Persona ${0} selected and auto-locked to current chat": "Персона ${0} выбрана и автоматически закреплена за этим чатом",
+ "This persona is only temporarily chosen. Click for more info.": "Данная персона выбрана лишь временно. Нажмите, чтобы узнать больше.",
+ "Temporary Persona": "Временная персона",
+ "A different persona is locked to this chat, or you have a different default persona set. The currently selected persona will only be temporary, and resets on reload. Consider locking this persona to the chat if you want to permanently use it.": "К этому чату уже привязана иная персона, либо у вас выбрана иная персона по-умолчанию. Выбранная в данный момент персона будет временной, и сбросится после перезагрузки. Если хотите всегда использовать её в этом чате, советуем её прикрепить.",
+ "Current Persona: ${0}": "Выбранная персона: ${0}",
+ "Chat persona: ${0}": "Персона для этого чата: ${0}",
+ "Default persona: ${0}": "Персона по умолчанию (стандартная): ${0}",
+ "Persona ${0} is now unlocked from this chat.": "Персона ${0} отвязана от этого чата.",
+ "Persona Unlocked": "Персона отвязана",
+ "Persona ${0} is now unlocked from character ${1}.": "Персона ${0} отвязана от персонажа ${1}.",
+ "Persona Not Found": "Персона не найдена",
+ "Persona Locked": "Персона закреплена",
+ "User persona ${0} is locked to character ${1}${2}": "Персона ${0} прикреплена к персонажу ${1}${2}",
+ "Persona Name Not Set": "У персоны отсутствует имя",
+ "You must bind a name to this persona before you can set a lorebook.": "Перед привязкой лорбука персоне необходимо присвоить имя.",
+ "Default Persona Removed": "Персона по умолчанию снята",
+ "Persona is locked to the current character": "Персона закреплена за этим персонажем",
+ "Persona is locked to the current chat": "Персона закреплена за этим чатом",
+ "characters": "перс.",
+ "character": "персонаж",
+ "in this group": "в группе",
+ "Chatting Since": "Первая беседа",
+ "Context": "Контекст",
+ "Response": "Ответ",
+ "Connected": "Подключено",
+ "Enter new background name:": "Введите новое название для фона:",
+ "AI Horde Website": "Сайт AI Horde",
+ "Enable web search": "Включить поиск в Интернете",
+ "Use search capabilities provided by the backend.": "Разрешить использование предоставляемых бэкендом функций поиска.",
+ "Request inline images": "Запрашивать inline-изображения",
+ "Allows the model to return image attachments.": "Разрешить модели отправлять вложения в виде картинок.",
+ "Request inline images_desc_2": "Не совместимо со следующим функционалом: вызов функций, поиск в Интернете, системный промпт.",
+ "Connected Personas": "Связанные персоны",
+ "[Currently no personas connected]": "[Связанных персон нет]",
+ "The following personas are connected to the current character.\n\nClick on a persona to select it for the current character.\nShift + Click to unlink the persona from the character.": "С этим персонажем связаны следующие персоны.\n\nНажмите на персону, чтобы выбрать её для данного персонажа.\nShift + ЛКМ, чтобы её отвязать.",
+ "Persona Connections": "Связи с персонами",
+ "Pooled order": "Если уже давно не отвечали",
+ "Attach a File": "Приложить файл",
+ "Attach a file or image to a current chat.": "Приложить файл или изображение к текущему чату",
+ "Remove the file": "Удалить файл",
+ "Delete the Chat File?": "Удалить чат?",
+ "Forbidden": "Доступ запрещён",
+ "To view your API keys here, set the value of allowKeysExposure to true in config.yaml file and restart the SillyTavern server.": "Чтобы видеть здесь ваши API-ключи, установите параметр allowKeysExposure в config.yaml в положение true, после чего перезапустите сервер SillyTavern.",
+ "Invalid endpoint URL. Requests may fail.": "Некорректный адрес эндпоинта. Запросы могут не проходить.",
+ "How to install extensions?": "Как устанавливать расширения?",
+ "Click the flashing button to install extensions.": "Чтобы их установить, нажмите на мигающую кнопку.",
+ "ext_regex_reasoning_desc": "Содержимое блоков рассуждений. При отмеченной галочке \"Только промпт\" будут также обработаны добавленные в промпт рассуждения.",
+ "Macro in Find Regex": "Макросы в рег. выражении",
+ "Don't substitute": "Не заменять",
+ "Substitute (raw)": "Заменять в \"чистом\" виде",
+ "Substitute (escaped)": "Заменять после экранирования",
+ "ext_regex_other_options_desc": "По умолчанию, расширение вносит изменения в сам файл чата.\nПри включении одной из опций (или обеих), файл чата останется нетронутым, при этом сами изменения по-прежнему будут действовать.",
+ "ext_regex_flags_help": "Нажмите, чтобы узнать больше о флагах в рег. выражениях.",
+ "Applies to all matches": "Заменяет все вхождения",
+ "Applies to the first match": "Заменяет первое вхождение",
+ "Case insensitive": "Не чувствительно к регистру",
+ "Case sensitive": "Чувствительно к регистру",
+ "Find Regex is empty": "Рег. выражение не указано",
+ "Click the button to save it as a file.": "Нажмите на кнопку справа, чтобы сохранить его в файл.",
+ "Export as JSONL": "Экспорт в формате JSONL"
}
diff --git a/public/script.js b/public/script.js
index 8d3784c71..ec0189e0a 100644
--- a/public/script.js
+++ b/public/script.js
@@ -1019,7 +1019,7 @@ export function displayOnlineStatus() {
$('.online_status_text').text($('#API-status-top').attr('no_connection_text'));
} else {
$('.online_status_indicator').addClass('success');
- $('.online_status_text').text(online_status);
+ $('.online_status_text').text(t([online_status]));
}
}
@@ -10417,7 +10417,7 @@ jQuery(async function () {
e.stopPropagation();
chat_file_for_del = $(this).attr('file_name');
console.debug('detected cross click for' + chat_file_for_del);
- callPopup('
Delete the Chat File? ', 'del_chat');
+ callPopup('
' + t`Delete the Chat File?` + ' ', 'del_chat');
});
$('#advanced_div').click(function () {
diff --git a/public/scripts/backgrounds.js b/public/scripts/backgrounds.js
index 3deef929a..308aac532 100644
--- a/public/scripts/backgrounds.js
+++ b/public/scripts/backgrounds.js
@@ -5,6 +5,7 @@ import { saveMetadataDebounced } from './extensions.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { flashHighlight, stringFormat } from './utils.js';
+import { t } from './i18n.js';
const BG_METADATA_KEY = 'custom_background';
const LIST_METADATA_KEY = 'chat_backgrounds';
@@ -243,7 +244,7 @@ async function getNewBackgroundName(referenceElement) {
const fileExtension = oldBg.split('.').pop();
const fileNameBase = isCustom ? oldBg.split('/').pop() : oldBg;
const oldBgExtensionless = fileNameBase.replace(`.${fileExtension}`, '');
- const newBgExtensionless = await callPopup('
Enter new background name: ', 'input', oldBgExtensionless);
+ const newBgExtensionless = await callPopup('
' + t`Enter new background name:` + ' ', 'input', oldBgExtensionless);
if (!newBgExtensionless) {
console.debug('no new_bg_extensionless');
diff --git a/public/scripts/bookmarks.js b/public/scripts/bookmarks.js
index 61baf34a4..6c916959e 100644
--- a/public/scripts/bookmarks.js
+++ b/public/scripts/bookmarks.js
@@ -358,7 +358,7 @@ export async function convertSoloToGroupChat() {
// Click on the freshly selected group to open it
await openGroupById(group.id);
- toastr.success('The chat has been successfully converted!');
+ toastr.success(t`The chat has been successfully converted!`);
}
/**
diff --git a/public/scripts/extensions/assets/index.js b/public/scripts/extensions/assets/index.js
index f50cca314..014db82cd 100644
--- a/public/scripts/extensions/assets/index.js
+++ b/public/scripts/extensions/assets/index.js
@@ -424,7 +424,7 @@ jQuery(async () => {
installHintButton.on('click', async function () {
const installButton = $('#third_party_extension_button');
flashHighlight(installButton, 5000);
- toastr.info('Click the flashing button to install extensions.', 'How to install extensions?');
+ toastr.info(t`Click the flashing button to install extensions.`, t`How to install extensions?`);
});
const connectButton = windowHtml.find('#assets-connect-button');
diff --git a/public/scripts/extensions/attachments/attach-button.html b/public/scripts/extensions/attachments/attach-button.html
index 8db0fa953..13bf1994b 100644
--- a/public/scripts/extensions/attachments/attach-button.html
+++ b/public/scripts/extensions/attachments/attach-button.html
@@ -1,4 +1,4 @@
-
+
Attach a File
diff --git a/public/scripts/extensions/regex/editor.html b/public/scripts/extensions/regex/editor.html
index 9d0e4ca6b..e059442bb 100644
--- a/public/scripts/extensions/regex/editor.html
+++ b/public/scripts/extensions/regex/editor.html
@@ -19,7 +19,7 @@
@@ -147,7 +147,7 @@
Ephemerality
-
+
diff --git a/public/scripts/extensions/regex/index.js b/public/scripts/extensions/regex/index.js
index 4805a6ed3..1a2e1595b 100644
--- a/public/scripts/extensions/regex/index.js
+++ b/public/scripts/extensions/regex/index.js
@@ -398,7 +398,7 @@ function runRegexCallback(args, value) {
for (const script of scripts) {
if (script.scriptName.toLowerCase() === scriptName.toLowerCase()) {
if (script.disabled) {
- toastr.warning(`Regex script "${scriptName}" is disabled.`);
+ toastr.warning(t`Regex script "${scriptName}" is disabled.`);
return value;
}
diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js
index c641da502..706b93194 100644
--- a/public/scripts/group-chats.js
+++ b/public/scripts/group-chats.js
@@ -696,7 +696,7 @@ export function getGroupBlock(group) {
template.find('.group_fav_icon').css('display', 'none');
template.addClass(group.fav ? 'is_fav' : '');
template.find('.ch_fav').val(group.fav);
- template.find('.group_select_counter').text(`${count} ${count != 1 ? 'characters' : 'character'}`);
+ template.find('.group_select_counter').text(count + ' ' + (count != 1 ? t`characters` : t`character`));
template.find('.group_select_block_list').text(namesList.join(', '));
// Display inline tags
diff --git a/public/scripts/horde.js b/public/scripts/horde.js
index 2fcee3ac6..fc5ca93f2 100644
--- a/public/scripts/horde.js
+++ b/public/scripts/horde.js
@@ -10,6 +10,7 @@ import { SECRET_KEYS, writeSecret } from './secrets.js';
import { delay } from './utils.js';
import { isMobile } from './RossAscends-mods.js';
import { autoSelectInstructPreset } from './instruct-mode.js';
+import { t } from './i18n.js';
export {
horde_settings,
@@ -169,7 +170,7 @@ async function adjustHordeGenerationParams(max_context_length, max_length) {
}
}
console.log(maxContextLength, maxLength);
- $('#adjustedHordeParams').text(`Context: ${maxContextLength}, Response: ${maxLength}`);
+ $('#adjustedHordeParams').text(t`Context` + `: ${maxContextLength}, ` + t`Response` + `: ${maxLength}`);
return { maxContextLength, maxLength };
}
@@ -177,7 +178,7 @@ function setContextSizePreview() {
if (horde_settings.models.length) {
adjustHordeGenerationParams(max_context, amount_gen);
} else {
- $('#adjustedHordeParams').text('Context: --, Response: --');
+ $('#adjustedHordeParams').text(t`Context` + ': --, ' + t`Response` + ': --');
}
}
@@ -404,7 +405,7 @@ jQuery(function () {
if (horde_settings.models.length) {
adjustHordeGenerationParams(max_context, amount_gen);
} else {
- $('#adjustedHordeParams').text('Context: --, Response: --');
+ $('#adjustedHordeParams').text(t`Context` + ': --, ' + t`Response` + ': --');
}
saveSettingsDebounced();
diff --git a/public/scripts/personas.js b/public/scripts/personas.js
index ce220ad5f..591b8d6ab 100644
--- a/public/scripts/personas.js
+++ b/public/scripts/personas.js
@@ -800,7 +800,7 @@ async function selectCurrentPersona({ toastPersonaNameChange = true } = {}) {
chat_metadata['persona'] = user_avatar;
console.log(`Auto locked persona to ${user_avatar}`);
if (toastPersonaNameChange && power_user.persona_show_notifications) {
- toastr.success(`Persona ${personaName} selected and auto-locked to current chat`, t`Persona Selected`);
+ toastr.success(t`Persona ${personaName} selected and auto-locked to current chat`, t`Persona Selected`);
}
saveMetadataDebounced();
updatePersonaUIStates();
diff --git a/public/scripts/samplerSelect.js b/public/scripts/samplerSelect.js
index 35f5629ba..0389abd6c 100644
--- a/public/scripts/samplerSelect.js
+++ b/public/scripts/samplerSelect.js
@@ -9,6 +9,7 @@ import { power_user } from './power-user.js';
//import { getSortableDelay, onlyUnique } from './utils.js';
//import { getCfgPrompt } from './cfg-scale.js';
import { setting_names } from './textgen-settings.js';
+import { renderTemplateAsync } from './templates.js';
const TGsamplerNames = setting_names;
@@ -25,25 +26,7 @@ async function showSamplerSelectPopup() {
const html = $(document.createElement('div'));
html.attr('id', 'sampler_view_list')
.addClass('flex-container flexFlowColumn');
- html.append(`
-
-
-
Sampler Select
-
-
-
-
-
-
Here you can toggle the display of individual samplers. (WIP)
-
- `);
+ html.append(await renderTemplateAsync('samplerSelector'));
const listContainer = $('
');
const APISamplers = await listSamplers(main_api);
diff --git a/public/scripts/secrets.js b/public/scripts/secrets.js
index 261c31480..8d95d62cc 100644
--- a/public/scripts/secrets.js
+++ b/public/scripts/secrets.js
@@ -1,5 +1,6 @@
import { DOMPurify } from '../lib.js';
import { callPopup, getRequestHeaders } from '../script.js';
+import { t } from './i18n.js';
export const SECRET_KEYS = {
HORDE: 'api_key_horde',
@@ -104,7 +105,7 @@ async function viewSecrets() {
});
if (response.status == 403) {
- callPopup('Forbidden To view your API keys here, set the value of allowKeysExposure to true in config.yaml file and restart the SillyTavern server.
', 'text');
+ callPopup('' + t`Forbidden` + ' ' + t`To view your API keys here, set the value of allowKeysExposure to true in config.yaml file and restart the SillyTavern server.` + '
', 'text');
return;
}
diff --git a/public/scripts/templates/assistantNote.html b/public/scripts/templates/assistantNote.html
index f17211245..7b2580743 100644
--- a/public/scripts/templates/assistantNote.html
+++ b/public/scripts/templates/assistantNote.html
@@ -1,9 +1,9 @@
Note: this chat is temporary and will be deleted as soon as you leave it.
- Click the button to save it as a file.
+ Click the button to save it as a file.
-
diff --git a/public/scripts/templates/samplerSelector.html b/public/scripts/templates/samplerSelector.html
new file mode 100644
index 000000000..cecd11cec
--- /dev/null
+++ b/public/scripts/templates/samplerSelector.html
@@ -0,0 +1,18 @@
+
+
+
Sampler Select
+
+
+
+
+
+
Here you can toggle the display of individual samplers. (WIP)
+
+
\ No newline at end of file
From b01e2824bee455bec63c156d03752cd89cc97f11 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Mon, 17 Mar 2025 09:46:52 +0000
Subject: [PATCH 12/21] Remove trim from /start-reply-with
---
public/scripts/power-user.js | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js
index 572be339b..c8659342f 100644
--- a/public/scripts/power-user.js
+++ b/public/scripts/power-user.js
@@ -4227,14 +4227,13 @@ $(document).ready(() => {
],
callback: (args, value) => {
const force = isTrueBoolean(String(args?.force ?? false));
- value = String(value ?? '').trim();
// Skip processing if no value and not forced
if (!force && !value) {
return power_user.user_prompt_bias;
}
- power_user.user_prompt_bias = value;
+ power_user.user_prompt_bias = String(value ?? '');
$('#start_reply_with').val(power_user.user_prompt_bias);
saveSettingsDebounced();
From 86de927ab95b3a777c5c7e3fdd98ce6921c8f2b7 Mon Sep 17 00:00:00 2001
From: bmen25124
Date: Mon, 17 Mar 2025 14:54:59 +0300
Subject: [PATCH 13/21] Added "custom_url" to ChatCompletionService
---
public/scripts/custom-request.js | 4 +++-
public/scripts/extensions/shared.js | 1 +
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/public/scripts/custom-request.js b/public/scripts/custom-request.js
index 60e8b8f7f..d6fccf1a9 100644
--- a/public/scripts/custom-request.js
+++ b/public/scripts/custom-request.js
@@ -41,6 +41,7 @@ import { formatInstructModeChat, formatInstructModePrompt, names_behavior_types
* @property {string} chat_completion_source - Source provider for chat completion
* @property {number} max_tokens - Maximum number of tokens to generate
* @property {number} [temperature] - Optional temperature parameter for response randomness
+ * @property {string} [custom_url] - Optional custom URL for chat completion
*/
/** @typedef {Record & ChatCompletionPayloadBase} ChatCompletionPayload */
@@ -264,7 +265,7 @@ export class ChatCompletionService {
* @param {ChatCompletionPayload} custom
* @returns {ChatCompletionPayload}
*/
- static createRequestData({ messages, model, chat_completion_source, max_tokens, temperature, ...props }) {
+ static createRequestData({ messages, model, chat_completion_source, max_tokens, temperature, custom_url, ...props }) {
const payload = {
...props,
messages,
@@ -272,6 +273,7 @@ export class ChatCompletionService {
chat_completion_source,
max_tokens,
temperature,
+ custom_url,
stream: false,
};
diff --git a/public/scripts/extensions/shared.js b/public/scripts/extensions/shared.js
index 3e7eef8a4..d5549c40a 100644
--- a/public/scripts/extensions/shared.js
+++ b/public/scripts/extensions/shared.js
@@ -323,6 +323,7 @@ export class ConnectionManagerRequestService {
max_tokens: maxTokens,
model: profile.model,
chat_completion_source: selectedApiMap.source,
+ custom_url: profile['api-url'],
}, {
presetName: includePreset ? profile.preset : undefined,
}, extractData);
From 1dade2970b0df52c4182abea24915879df1a6226 Mon Sep 17 00:00:00 2001
From: Yokayo
Date: Mon, 17 Mar 2025 20:21:11 +0700
Subject: [PATCH 14/21] Reasoning keys
---
public/locales/ru-ru.json | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/public/locales/ru-ru.json b/public/locales/ru-ru.json
index 6960cba3d..560124175 100644
--- a/public/locales/ru-ru.json
+++ b/public/locales/ru-ru.json
@@ -2320,5 +2320,22 @@
"Case sensitive": "Чувствительно к регистру",
"Find Regex is empty": "Рег. выражение не указано",
"Click the button to save it as a file.": "Нажмите на кнопку справа, чтобы сохранить его в файл.",
- "Export as JSONL": "Экспорт в формате JSONL"
+ "Export as JSONL": "Экспорт в формате JSONL",
+ "Thought for some time": "Какое-то время заняли размышления",
+ "Thinking...": "В раздумьях...",
+ "Thought for ${0}": "Размышления заняли ${0}",
+ "Hidden reasoning - Add reasoning block": "Рассуждения скрыты - Добавить блок рассуждений",
+ "Add reasoning block": "Добавить блок рассуждений",
+ "Edit reasoning": "Редактировать рассуждения",
+ "Copy reasoning": "Скопировать рассуждения",
+ "Confirm Edit": "Подтвердить",
+ "Remove reasoning": "Удалить рассуждения",
+ "Cancel edit": "Отменить редактирование",
+ "Remove Reasoning": "Удалить рассуждения",
+ "Are you sure you want to clear the reasoning? Visible message contents will stay intact.": "Вы точно хотите удалить блок рассуждений? Основное сообщение останется на месте.",
+ "Reasoning Parse": "Парсинг рассуждений",
+ "Both prefix and suffix must be set in the Reasoning Formatting settings.": "В настройках форматирования рассуждений должны быть заданы префикс и суффикс.",
+ "Invalid return type '${0}', defaulting to 'reasoning'.": "Некорректный возвращаемый тип, используем стандартный 'reasoning'.",
+ "Reasoning already exists.": "Рассуждения уже присутствуют.",
+ "Edit Message": "Редактирование"
}
From e7c9960a45cf459e4a21f9865f1d94d5866bdf9c Mon Sep 17 00:00:00 2001
From: Yokayo
Date: Tue, 18 Mar 2025 02:15:13 +0700
Subject: [PATCH 15/21] Fix
---
public/locales/ru-ru.json | 4 +++-
public/script.js | 10 +++++-----
public/scripts/openai.js | 6 +++---
3 files changed, 11 insertions(+), 9 deletions(-)
diff --git a/public/locales/ru-ru.json b/public/locales/ru-ru.json
index 560124175..db905c2bd 100644
--- a/public/locales/ru-ru.json
+++ b/public/locales/ru-ru.json
@@ -2337,5 +2337,7 @@
"Both prefix and suffix must be set in the Reasoning Formatting settings.": "В настройках форматирования рассуждений должны быть заданы префикс и суффикс.",
"Invalid return type '${0}', defaulting to 'reasoning'.": "Некорректный возвращаемый тип, используем стандартный 'reasoning'.",
"Reasoning already exists.": "Рассуждения уже присутствуют.",
- "Edit Message": "Редактирование"
+ "Edit Message": "Редактирование",
+ "Status check bypassed": "Проверка статуса отключена",
+ "Valid": "Работает"
}
diff --git a/public/script.js b/public/script.js
index ec0189e0a..c20791642 100644
--- a/public/script.js
+++ b/public/script.js
@@ -1019,7 +1019,7 @@ export function displayOnlineStatus() {
$('.online_status_text').text($('#API-status-top').attr('no_connection_text'));
} else {
$('.online_status_indicator').addClass('success');
- $('.online_status_text').text(t([online_status]));
+ $('.online_status_text').text(online_status);
}
}
@@ -1140,7 +1140,7 @@ export async function clearItemizedPrompts() {
async function getStatusHorde() {
try {
const hordeStatus = await checkHordeStatus();
- setOnlineStatus(hordeStatus ? 'Connected' : 'no_connection');
+ setOnlineStatus(hordeStatus ? t`Connected` : 'no_connection');
}
catch {
setOnlineStatus('no_connection');
@@ -1207,7 +1207,7 @@ async function getStatusTextgen() {
}
if ([textgen_types.GENERIC, textgen_types.OOBA].includes(textgen_settings.type) && textgen_settings.bypass_status_check) {
- setOnlineStatus('Status check bypassed');
+ setOnlineStatus(t`Status check bypassed`);
return resultCheckStatus();
}
@@ -1232,7 +1232,7 @@ async function getStatusTextgen() {
setOnlineStatus(textgen_settings.togetherai_model);
} else if (textgen_settings.type === textgen_types.OLLAMA) {
loadOllamaModels(data?.data);
- setOnlineStatus(textgen_settings.ollama_model || 'Connected');
+ setOnlineStatus(textgen_settings.ollama_model || t`Connected`);
} else if (textgen_settings.type === textgen_types.INFERMATICAI) {
loadInfermaticAIModels(data?.data);
setOnlineStatus(textgen_settings.infermaticai_model);
@@ -1256,7 +1256,7 @@ async function getStatusTextgen() {
setOnlineStatus(textgen_settings.tabby_model || data?.result);
} else if (textgen_settings.type === textgen_types.GENERIC) {
loadGenericModels(data?.data);
- setOnlineStatus(textgen_settings.generic_model || data?.result || 'Connected');
+ setOnlineStatus(textgen_settings.generic_model || data?.result || t`Connected`);
} else {
setOnlineStatus(data?.result);
}
diff --git a/public/scripts/openai.js b/public/scripts/openai.js
index 112726562..cb570f357 100644
--- a/public/scripts/openai.js
+++ b/public/scripts/openai.js
@@ -3476,7 +3476,7 @@ async function getStatusOpen() {
let status;
if ('ai' in window) {
- status = 'Valid';
+ status = t`Valid`;
}
else {
showWindowExtensionError();
@@ -3525,7 +3525,7 @@ async function getStatusOpen() {
const canBypass = (oai_settings.chat_completion_source === chat_completion_sources.OPENAI && oai_settings.bypass_status_check) || oai_settings.chat_completion_source === chat_completion_sources.CUSTOM;
if (canBypass) {
- setOnlineStatus('Status check bypassed');
+ setOnlineStatus(t`Status check bypassed`);
}
try {
@@ -3547,7 +3547,7 @@ async function getStatusOpen() {
saveModelList(responseData.data);
}
if (!('error' in responseData)) {
- setOnlineStatus('Valid');
+ setOnlineStatus(t`Valid`);
}
} catch (error) {
console.error(error);
From ca53323a084a4269572f9d5a94f36d163adac843 Mon Sep 17 00:00:00 2001
From: Wolfsblvt
Date: Mon, 17 Mar 2025 20:59:08 +0100
Subject: [PATCH 16/21] Fix deleting swipes overwriting reasoning
- Well, someone forgot about syncing extras and mes data again....
- Built the oppositive function of `syncMesToSwipe`, so we can now call `syncSwipeToMes`
---
public/script.js | 68 +++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 64 insertions(+), 4 deletions(-)
diff --git a/public/script.js b/public/script.js
index a0de9af2d..e8f81e105 100644
--- a/public/script.js
+++ b/public/script.js
@@ -6276,7 +6276,6 @@ export function syncMesToSwipe(messageId = null) {
}
const targetMessage = chat[targetMessageId];
-
if (!targetMessage) {
return false;
}
@@ -6309,6 +6308,68 @@ export function syncMesToSwipe(messageId = null) {
return true;
}
+/**
+ * Syncs swipe data back to the message data at the given message ID (or the last message if no ID is given).
+ * If the swipe ID is not provided, the current swipe ID in the message object is used.
+ *
+ * If the swipe data is invalid in some way, this function will exit out without doing anything.
+ * @param {number?} [messageId=null] - The ID of the message to sync with the swipe data. If no ID is given, the last message is used.
+ * @param {number?} [swipeId=null] - The ID of the swipe to sync. If no ID is given, the current swipe ID in the message object is used.
+ * @returns {boolean} Whether the swipe data was successfully synced to the message
+ */
+export function syncSwipeToMes(messageId = null, swipeId = null) {
+ if (!chat.length) {
+ return false;
+ }
+
+ const targetMessageId = messageId ?? chat.length - 1;
+ if (targetMessageId >= chat.length || targetMessageId < 0) {
+ console.warn(`[syncSwipeToMes] Invalid message ID: ${messageId}`);
+ return false;
+ }
+
+ const targetMessage = chat[targetMessageId];
+ if (!targetMessage) {
+ return false;
+ }
+
+ if (swipeId !== null) {
+ if (isNaN(swipeId) || swipeId < 0) {
+ console.warn(`[syncSwipeToMes] Invalid swipe ID: ${swipeId}`);
+ return false;
+ }
+ targetMessage.swipe_id = swipeId;
+ }
+
+ // No swipe data there yet, exit out
+ if (typeof targetMessage.swipe_id !== 'number') {
+ return false;
+ }
+ // If swipes structure is invalid, exit out
+ if (!Array.isArray(targetMessage.swipe_info) || !Array.isArray(targetMessage.swipes)) {
+ return false;
+ }
+
+ const targetSwipeId = targetMessage.swipe_id;
+ if (!targetMessage.swipes[targetSwipeId] || !targetMessage.swipe_info[targetSwipeId]) {
+ console.warn(`[syncSwipeToMes] Invalid swipe ID: ${targetSwipeId}`);
+ return false;
+ }
+
+ const targetSwipeInfo = targetMessage.swipe_info[targetSwipeId];
+ if (typeof targetSwipeInfo !== 'object') {
+ return false;
+ }
+
+ targetMessage.mes = targetMessage.swipes[targetSwipeId];
+ targetMessage.send_date = targetSwipeInfo.send_date;
+ targetMessage.gen_started = targetSwipeInfo.gen_started;
+ targetMessage.gen_finished = targetSwipeInfo.gen_finished;
+ targetMessage.extra = structuredClone(targetSwipeInfo.extra);
+
+ return true;
+}
+
/**
* Saves the image to the message object.
* @param {ParsedImage} img Image object
@@ -8317,10 +8378,9 @@ export async function deleteSwipe(swipeId = null) {
lastMessage.swipe_info.splice(swipeId, 1);
}
- // Select the next swip, or the one before if it was the last one
+ // Select the next swipe, or the one before if it was the last one
const newSwipeId = Math.min(swipeId, lastMessage.swipes.length - 1);
- lastMessage.swipe_id = newSwipeId;
- lastMessage.mes = lastMessage.swipes[newSwipeId];
+ syncSwipeToMes(null, newSwipeId);
await saveChatConditional();
await reloadCurrentChat();
From 5585220d0a8f74039aeb93206a2ceb040548122a Mon Sep 17 00:00:00 2001
From: Wolfsblvt
Date: Mon, 17 Mar 2025 23:23:58 +0100
Subject: [PATCH 17/21] Fix Expression Override not resetting if empty
- When switching chars, override field gets correctly loaded. The display value won't be reset when the override was empty. This was likely unintended.
---
public/scripts/extensions/expressions/index.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/public/scripts/extensions/expressions/index.js b/public/scripts/extensions/expressions/index.js
index fc1294f87..aa592cfd7 100644
--- a/public/scripts/extensions/expressions/index.js
+++ b/public/scripts/extensions/expressions/index.js
@@ -2154,7 +2154,7 @@ function migrateSettings() {
imgElement.src = '';
}
- setExpressionOverrideHtml();
+ setExpressionOverrideHtml(true); // force-clear, as the character might not have an override defined
if (isVisualNovelMode()) {
$('#visual-novel-wrapper').empty();
From 6ff06ff04bded54c846ce203192243c0908d74b8 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Tue, 18 Mar 2025 00:29:03 +0200
Subject: [PATCH 18/21] Use performFuzzySearch
---
public/scripts/reasoning.js | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/public/scripts/reasoning.js b/public/scripts/reasoning.js
index 7b688d92f..feda8a417 100644
--- a/public/scripts/reasoning.js
+++ b/public/scripts/reasoning.js
@@ -1,5 +1,4 @@
import {
- Fuse,
moment,
} from '../lib.js';
import { chat, closeMessageEditor, event_types, eventSource, main_api, messageFormatting, saveChatConditional, saveChatDebounced, saveSettingsDebounced, substituteParams, syncMesToSwipe, updateMessageBlock } from '../script.js';
@@ -8,7 +7,7 @@ import { getCurrentLocale, t, translate } from './i18n.js';
import { MacrosParser } from './macros.js';
import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js';
import { Popup } from './popup.js';
-import { power_user } from './power-user.js';
+import { performFuzzySearch, power_user } from './power-user.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
@@ -91,7 +90,7 @@ export function extractReasoningFromData(data, {
mainApi = null,
ignoreShowThoughts = false,
textGenType = null,
- chatCompletionSource = null
+ chatCompletionSource = null,
} = {}) {
switch (mainApi ?? main_api) {
case 'textgenerationwebui':
@@ -781,8 +780,7 @@ function selectReasoningTemplateCallback(args, name) {
let foundName = templateNames.find(x => x.toLowerCase() === name.toLowerCase());
if (!foundName) {
- const fuse = new Fuse(templateNames);
- const result = fuse.search(name);
+ const result = performFuzzySearch('reasoning-templates', templateNames, [], name);
if (result.length === 0) {
!quiet && toastr.warning(`Reasoning template "${name}" not found`);
From 271c93a504cbbeb9d71d16c82f58136027d92624 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Tue, 18 Mar 2025 00:32:31 +0200
Subject: [PATCH 19/21] Rename DeepSeek template, add Blank reasoning template
---
default/content/index.json | 6 +++++-
default/content/presets/reasoning/Blank.json | 6 ++++++
.../presets/reasoning/{DeepSeek R1.json => DeepSeek.json} | 2 +-
public/scripts/power-user.js | 2 +-
public/scripts/reasoning.js | 2 +-
5 files changed, 14 insertions(+), 4 deletions(-)
create mode 100644 default/content/presets/reasoning/Blank.json
rename default/content/presets/reasoning/{DeepSeek R1.json => DeepSeek.json} (75%)
diff --git a/default/content/index.json b/default/content/index.json
index 3a09e8e84..49e2dd12c 100644
--- a/default/content/index.json
+++ b/default/content/index.json
@@ -788,7 +788,11 @@
"type": "context"
},
{
- "filename": "presets/reasoning/DeepSeek R1.json",
+ "filename": "presets/reasoning/DeepSeek.json",
+ "type": "reasoning"
+ },
+ {
+ "filename": "presets/reasoning/Blank.json",
"type": "reasoning"
}
]
diff --git a/default/content/presets/reasoning/Blank.json b/default/content/presets/reasoning/Blank.json
new file mode 100644
index 000000000..bdb2fa032
--- /dev/null
+++ b/default/content/presets/reasoning/Blank.json
@@ -0,0 +1,6 @@
+{
+ "name": "Blank",
+ "prefix": "",
+ "suffix": "",
+ "separator": ""
+}
diff --git a/default/content/presets/reasoning/DeepSeek R1.json b/default/content/presets/reasoning/DeepSeek.json
similarity index 75%
rename from default/content/presets/reasoning/DeepSeek R1.json
rename to default/content/presets/reasoning/DeepSeek.json
index 503c45171..d2f78c0ce 100644
--- a/default/content/presets/reasoning/DeepSeek R1.json
+++ b/default/content/presets/reasoning/DeepSeek.json
@@ -1,5 +1,5 @@
{
- "name": "DeepSeek R1",
+ "name": "DeepSeek",
"prefix": "\n",
"suffix": "\n ",
"separator": "\n\n"
diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js
index 093d978a2..13bda052f 100644
--- a/public/scripts/power-user.js
+++ b/public/scripts/power-user.js
@@ -258,7 +258,7 @@ let power_user = {
},
reasoning: {
- name: 'DeepSeek R1',
+ name: 'DeepSeek',
auto_parse: false,
add_to_prompts: false,
auto_expand: false,
diff --git a/public/scripts/reasoning.js b/public/scripts/reasoning.js
index feda8a417..39ce8885e 100644
--- a/public/scripts/reasoning.js
+++ b/public/scripts/reasoning.js
@@ -957,7 +957,7 @@ function registerReasoningSlashCommands() {
Example:
From b1346910a451cebf70257f6fb7bbd230f876d354 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Tue, 18 Mar 2025 00:47:20 +0200
Subject: [PATCH 20/21] Adjust reasoning template migration procedure
---
public/scripts/power-user.js | 4 ++--
public/scripts/reasoning.js | 29 +++++++++++++++++++++++++++--
2 files changed, 29 insertions(+), 4 deletions(-)
diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js
index 13bda052f..81d5bbf94 100644
--- a/public/scripts/power-user.js
+++ b/public/scripts/power-user.js
@@ -55,7 +55,7 @@ import { POPUP_TYPE, callGenericPopup } from './popup.js';
import { loadSystemPrompts } from './sysprompt.js';
import { fuzzySearchCategories } from './filters.js';
import { accountStorage } from './util/AccountStorage.js';
-import { loadReasoningTemplates } from './reasoning.js';
+import { DEFAULT_REASONING_TEMPLATE, loadReasoningTemplates } from './reasoning.js';
export {
loadPowerUserSettings,
@@ -258,7 +258,7 @@ let power_user = {
},
reasoning: {
- name: 'DeepSeek',
+ name: DEFAULT_REASONING_TEMPLATE,
auto_parse: false,
add_to_prompts: false,
auto_expand: false,
diff --git a/public/scripts/reasoning.js b/public/scripts/reasoning.js
index 39ce8885e..b63b26f63 100644
--- a/public/scripts/reasoning.js
+++ b/public/scripts/reasoning.js
@@ -8,6 +8,7 @@ import { MacrosParser } from './macros.js';
import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js';
import { Popup } from './popup.js';
import { performFuzzySearch, power_user } from './power-user.js';
+import { getPresetManager } from './preset-manager.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
@@ -29,6 +30,8 @@ import { copyText, escapeRegex, isFalseBoolean, isTrueBoolean, setDatasetPropert
*/
export const reasoning_templates = [];
+export const DEFAULT_REASONING_TEMPLATE = 'DeepSeek';
+
/**
* @type {Record>} List of UI elements for reasoning settings
* @readonly
@@ -1337,8 +1340,30 @@ export async function loadReasoningTemplates(data) {
$('').val(template.name).text(template.name).appendTo(UI.$select);
}
- if (!power_user.reasoning.name) {
- power_user.reasoning.name = reasoning_templates[0]?.name ?? '';
+ // No template name, need to migrate
+ if (power_user.reasoning.name === undefined) {
+ const defaultTemplate = reasoning_templates.find(p => p.name === DEFAULT_REASONING_TEMPLATE);
+ if (defaultTemplate) {
+ // If the reasoning settings were modified - migrate them to a custom template
+ if (power_user.reasoning.prefix !== defaultTemplate.prefix || power_user.reasoning.suffix !== defaultTemplate.suffix || power_user.reasoning.separator !== defaultTemplate.separator) {
+ /** @type {ReasoningTemplate} */
+ const data = {
+ name: '[Migrated] Custom',
+ prefix: power_user.reasoning.prefix,
+ suffix: power_user.reasoning.suffix,
+ separator: power_user.reasoning.separator,
+ };
+ await getPresetManager('reasoning')?.savePreset(data.name, data);
+ power_user.reasoning.name = data.name;
+ } else {
+ power_user.reasoning.name = defaultTemplate.name;
+ }
+ } else {
+ // Template not found (deleted or content check skipped - leave blank)
+ power_user.reasoning.name = '';
+ }
+
+ saveSettingsDebounced();
}
UI.$select.val(power_user.reasoning.name);
From 594a3720ad6116548b9043212334d2dfe9eb099c Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Tue, 18 Mar 2025 00:59:04 +0200
Subject: [PATCH 21/21] Fix TTS
---
public/scripts/extensions/tts/index.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js
index b0b6fb198..9f3b38727 100644
--- a/public/scripts/extensions/tts/index.js
+++ b/public/scripts/extensions/tts/index.js
@@ -1207,8 +1207,8 @@ jQuery(async function () {
eventSource.on(event_types.GROUP_UPDATED, onChatChanged);
eventSource.on(event_types.GENERATION_STARTED, onGenerationStarted);
eventSource.on(event_types.GENERATION_ENDED, onGenerationEnded);
- eventSource.makeLast(event_types.CHARACTER_MESSAGE_RENDERED, onMessageEvent);
- eventSource.makeLast(event_types.USER_MESSAGE_RENDERED, onMessageEvent);
+ eventSource.makeLast(event_types.CHARACTER_MESSAGE_RENDERED, (messageId) => onMessageEvent(messageId));
+ eventSource.makeLast(event_types.USER_MESSAGE_RENDERED, (messageId) => onMessageEvent(messageId));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'speak',
callback: async (args, value) => {