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. +
+
+ Example: +
    +
  • +
    /reasoning-template DeepSeek R1
    +
  • +
+
+ `, + })); } 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) { + $('
- 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 - +