Merge remote-tracking branch 'origin/staging' into InfermaticAI
This commit is contained in:
commit
030806bf1e
|
@ -12,7 +12,7 @@
|
|||
"dependencies": {
|
||||
"@agnai/sentencepiece-js": "^1.1.1",
|
||||
"@agnai/web-tokenizers": "^0.1.3",
|
||||
"@dqbd/tiktoken": "^1.0.2",
|
||||
"@dqbd/tiktoken": "^1.0.13",
|
||||
"bing-translate-api": "^2.9.1",
|
||||
"body-parser": "^1.20.2",
|
||||
"command-exists": "^1.2.9",
|
||||
|
@ -78,9 +78,9 @@
|
|||
"integrity": "sha512-KlmTftToTtmb6aLVdne4NluS+POWputPF5J8v25UN/EQS+K9vahWEIe1NPRSFqBQclObkqHaj7JOnFrmnSm5MA=="
|
||||
},
|
||||
"node_modules/@dqbd/tiktoken": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@dqbd/tiktoken/-/tiktoken-1.0.7.tgz",
|
||||
"integrity": "sha512-bhR5k5W+8GLzysjk8zTMVygQZsgvf7W1F0IlL4ZQ5ugjo5rCyiwGM5d8DYriXspytfu98tv59niang3/T+FoDw=="
|
||||
"version": "1.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@dqbd/tiktoken/-/tiktoken-1.0.13.tgz",
|
||||
"integrity": "sha512-941kjlHjfI97l6NuH/AwuXV4mHuVnRooDcHNSlzi98hz+4ug3wT4gJcWjSwSZHqeGAEn90lC9sFD+8a9d5Jvxg=="
|
||||
},
|
||||
"node_modules/@eslint-community/eslint-utils": {
|
||||
"version": "4.4.0",
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"dependencies": {
|
||||
"@agnai/sentencepiece-js": "^1.1.1",
|
||||
"@agnai/web-tokenizers": "^0.1.3",
|
||||
"@dqbd/tiktoken": "^1.0.2",
|
||||
"@dqbd/tiktoken": "^1.0.13",
|
||||
"bing-translate-api": "^2.9.1",
|
||||
"body-parser": "^1.20.2",
|
||||
"command-exists": "^1.2.9",
|
||||
|
|
350
public/i18n.json
350
public/i18n.json
|
@ -1668,7 +1668,7 @@
|
|||
"Delete persona": "주인공 삭제하기"
|
||||
},
|
||||
"ru-ru": {
|
||||
"clickslidertips": "Можно установить вручную, использовав цифру рядом с ползунком",
|
||||
"clickslidertips": "Щелкните на цифру ползунка, чтобы вписать вручную.",
|
||||
"kobldpresets": "Предустановки Kobold",
|
||||
"guikoboldaisettings": "Интерфейс KoboldAI",
|
||||
"novelaipreserts": "Предустановки NovelAI",
|
||||
|
@ -1680,11 +1680,45 @@
|
|||
"context size(tokens)": "Размер контекста (в токенах)",
|
||||
"unlocked": "Неограниченный",
|
||||
"Only select models support context sizes greater than 4096 tokens. Increase only if you know what you're doing.": "Только отдельные модели поддерживают контекст, превышающий 4096 токенов. Используйте только если понимаете, что делаете.",
|
||||
"rep.pen": "Rep. Pen.",
|
||||
"rep.pen range": "Диапазон Rep. Pen.",
|
||||
"temperature": "Температура",
|
||||
"Encoder Rep. Pen.": "Расшифровщик Rep. Pen.",
|
||||
"No Repeat Ngram Size": "No Repeat Ngram Size",
|
||||
"rep.pen": "Штраф за повторение",
|
||||
"WI Entry Status:🔵 Constant🟢 Normal❌ Disabled": "Статус входа WI:\n 🔵 Константа\n 🟢 Cтандартный\n ❌ Отключен",
|
||||
"rep.pen range": "Диапазон штрафов за повтор.",
|
||||
"Temperature controls the randomness in token selection": "Temperature контролирует случайность выбора токенов:\n- низкая Temperature (<1.0) приводит к более предсказуемому тексту, отдавая предпочтение токенам с высокой вероятностью.\n- высокая Temperature (>1.0) повышает креативность и разнообразие вывода, давая токенам с низкой вероятностью больше шансов.\nУстановите значение 1.0 для исходных вероятностей.",
|
||||
"temperature": "Temperature",
|
||||
"Top K sets a maximum amount of top tokens that can be chosen from": "Top K задает максимальное количество токенов, которые могут быть выбраны.\nЕсли Top-K равен 20, это означает, что будут сохранены только 20 токенов с наивысшим рейтингом (независимо от того, что их вероятности разнообразны или ограничены)\nУстановите значение 0, чтобы отключить.",
|
||||
"Top P (a.k.a. nucleus sampling)": "Top P (также известная как выборка ядра) складывает все верхние токены, необходимые для достижения целевого процента.\nТо есть, если 2 верхних токена составляют 25%, а Top-P равен 0.50, учитываются только эти 2 верхних токена.\nУстановите значение 1.0, чтобы отключить.",
|
||||
"Typical P Sampling prioritizes tokens based on their deviation from the average entropy of the set": "Сэмплер Typical P определяет приоритет токенов на основе их отклонения от средней энтропии набора.\nОстаются токены, чья кумулятивная вероятность близка к заданному порогу (например, 0,5), выделяя те, которые имеют среднее информационное содержание.\nУстановите значение 1.0, чтобы отключить.",
|
||||
"Min P sets a base minimum probability": "Min P устанавливает базовую минимальную вероятность. Она масштабируется в зависимости от вероятности верхнего токена.\nЕсли вероятность верхнего токена составляет 80%, а Min P - 0.1, будут рассматриваться только токены с вероятностью выше 8%.\nУстановите значение 0, чтобы отключить.",
|
||||
"Top A sets a threshold for token selection based on the square of the highest token probability": "Top A устанавливает порог для отбора токенов на основе квадрата наибольшей вероятности токена.\nЕсли значение Top A равно 0.2, а вероятность верхнего токена равна 50%, то токены с вероятностью ниже 5% (0.2 * 0.5^2) будут исключены.\nУстановите значение 0, чтобы отключить.",
|
||||
"Tail-Free Sampling (TFS)": "Tail-Free Sampling (TFS) ищет хвост маловероятных токнов в распределении,\n анализируя скорость изменения вероятностей токенов с помощью производных. Он сохраняет токены до порога (например, 0.3), основанного на нормированной второй производной.\nЧем ближе к 0, тем больше отброшенных токенов. Установите значение 1.0, чтобы отключить.",
|
||||
"Epsilon cutoff sets a probability floor below which tokens are excluded from being sampled": "Epsilon cutoff устанавливает уровень вероятности, ниже которого токены исключаются из выборки.\nВ единицах 1e-4; разумное значение - 3.\nУстановите 0, чтобы отключить.",
|
||||
"Scale Temperature dynamically per token, based on the variation of probabilities": "Динамическое масштабирование Temperature для каждого токена, основанное на изменении вероятностей.",
|
||||
"Minimum Temp": "Минимальная Temp",
|
||||
"Maximum Temp": "Максимальная Temp",
|
||||
"Exponent": "Экспонента",
|
||||
"Mirostat Mode": "Режим",
|
||||
"Mirostat Tau": "Tau",
|
||||
"Mirostat Eta": "Eta",
|
||||
"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": "Temperature Last",
|
||||
"Use the temperature sampler last": "Использовать Temperature сэмплер в последнюю очередь. Это почти всегда разумно.\nПри включении: сначала выборка набора правдоподобных токенов, затем применение Temperature для корректировки их относительных вероятностей (технически, логитов).\nПри отключении: сначала применение Temperature для корректировки относительных вероятностей ВСЕХ токенов, затем выборка правдоподобных токенов из этого.\nОтключение Temperature Last увеличивает вероятности в хвосте распределения, что увеличивает шансы получить несогласованный ответ.",
|
||||
"LLaMA / Mistral / Yi models only": "Только для моделей LLaMA / Mistral / Yi. Убедитесь, что сначала выбрали подходящий токенизатор.\nПоследовательности, которые вы не хотите видеть в выходных данных.\nОдна на строку. Текст или [идентификаторы токенов].\nМногие токены имеют пробел впереди. Используйте счетчик токенов, если не уверены.",
|
||||
"Example: some text [42, 69, 1337]": "Пример:\nкакой-то текст\n[42, 69, 1337]",
|
||||
"Classifier Free Guidance. More helpful tip coming soon": "Руководство без классификатора. Больше полезных советов в ближайшее время.",
|
||||
"Scale": "Масштаб",
|
||||
"GBNF Grammar": "Грамматика GBNF",
|
||||
"Usage Stats": "Статистика исп.",
|
||||
"Click for stats!": "Нажмите для получения статистики!",
|
||||
"Backup": "Резер. копирование",
|
||||
"Backup your personas to a file": "Резервное копирование персон в файл",
|
||||
"Restore": "Восстановить",
|
||||
"Restore your personas from a file": "Восстановление персон из файла",
|
||||
"Type in the desired custom grammar": "Введите нужную пользовательскую грамматику",
|
||||
"Encoder Rep. Pen.": "Штраф за кодирование",
|
||||
"Smoothing Factor": "Коэффициент сглаживания",
|
||||
"No Repeat Ngram Size": "Нет повторов размера Ngram",
|
||||
"Min Length": "Минимальная длина",
|
||||
"OpenAI Reverse Proxy": "Прокси с OpenAI",
|
||||
"Alternative server URL (leave empty to use the default value).": "Альтернативный URL сервера (оставьте пустым для стандартного значения)",
|
||||
|
@ -1694,21 +1728,21 @@
|
|||
"Enable this if the streaming doesn't work with your proxy": "Включите это, если потоковый вывод текста не работает с вашим прокси",
|
||||
"Context Size (tokens)": "Размер контекста (в токенах)",
|
||||
"Max Response Length (tokens)": "Максимальная длина ответа (в токенах)",
|
||||
"Temperature": "Температура",
|
||||
"Temperature": "Temperature",
|
||||
"Frequency Penalty": "Штраф за частоту",
|
||||
"Presence Penalty": "Штраф за присутствие",
|
||||
"Top-p": "Top-p",
|
||||
"Top-p": "Top P",
|
||||
"Display bot response text chunks as they are generated": "Отображать ответ ИИ по мере генерации текста",
|
||||
"Top A": "Top-a",
|
||||
"Typical Sampling": "Типичная выборка",
|
||||
"Tail Free Sampling": "Бесхвостовая выборка",
|
||||
"Rep. Pen. Slope": "Rep. Pen. Склон",
|
||||
"Top A": "Top А",
|
||||
"Typical Sampling": "Typical Sampling",
|
||||
"Tail Free Sampling": "Tail Free Sampling",
|
||||
"Rep. Pen. Slope": "Rep. Pen. Slope",
|
||||
"Single-line mode": "Режим одной строки",
|
||||
"Top K": "Top-k",
|
||||
"Top P": "Top-p",
|
||||
"Top K": "Top K",
|
||||
"Top P": "Top P",
|
||||
"Do Sample": "Сделать образец",
|
||||
"Add BOS Token": "Добавить BOS-токен",
|
||||
"Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative.": "Добавлять 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-токена вынудит модель никогда не завершать генерацию преждевременно",
|
||||
"Skip Special Tokens": "Пропускать специальные токены",
|
||||
|
@ -1717,12 +1751,13 @@
|
|||
"Length Penalty": "Штраф за длину",
|
||||
"Early Stopping": "Преждевременная остановка",
|
||||
"Contrastive search": "Контрастный поиск",
|
||||
"Penalty Alpha": "Штраф Альфа",
|
||||
"Penalty Alpha": "Penalty Alpha",
|
||||
"Seed": "Зерно",
|
||||
"Epsilon Cutoff": "Отсечение эпсилона",
|
||||
"Eta Cutoff": "Отсечка Eta",
|
||||
"Epsilon Cutoff": "Epsilon Cutoff",
|
||||
"Eta Cutoff": "Eta Cutoff",
|
||||
"Negative Prompt": "Отрицательная подсказка",
|
||||
"Mirostat (mode=1 is only for llama.cpp)": "Mirostat (режим = 1 только для llama.cpp)",
|
||||
"Mirostat (mode=1 is only for llama.cpp)": "Mirostat",
|
||||
"Mirostat is a thermostat for output perplexity": "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.",
|
||||
"Add text here that would make the AI generate things you don't want in your outputs.": "Добавьте сюда текст, который заставит ИИ генерировать то, что вы не хотите видеть в своих выводах",
|
||||
"Phrase Repetition Penalty": "Штраф за повторение фразы",
|
||||
"Preamble": "Преамбула",
|
||||
|
@ -1739,8 +1774,14 @@
|
|||
"NSFW Prioritized": "Предпочитать NSFW",
|
||||
"NSFW prompt text goes first in the prompt to emphasize its effect.": "Отправлять NSFW-инструкцию в начале для усиления его эффекта",
|
||||
"Streaming": "Потоковый вывод текста",
|
||||
"Display the response bit by bit as it is generated.": "Отображать ответ по кускам в процессе генерации.",
|
||||
"When this is off, responses will be displayed all at once when they are complete.": "Если данная функция отключена, ответ будет отображен полностью после генерации.",
|
||||
"Dynamic Temperature": "Динамическая Temperature",
|
||||
"Restore current preset": "Восстановить текущую предустановку",
|
||||
"Neutralize Samplers": "Нейтрализовать сэмплеры",
|
||||
"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 4096 tokens": "Включите эту опцию, только если ваша модель поддерживает размер контекста более 4096 токенов.\nУвеличивайте только если вы знаете, что делаете.",
|
||||
"Display the response bit by bit as it is generated": "Отображение ответа бит за битом по мере его генерации.\nКогда этот параметр выключен, ответы будут отображаться все сразу после их завершения.",
|
||||
"Generate only one line per request (KoboldAI only, ignored by KoboldCpp).": "Генерируйте только одну строку для каждого запроса (только KoboldAI, игнорируется KoboldCpp).",
|
||||
"Ban the End-of-Sequence (EOS) token (with KoboldCpp, and possibly also other tokens with KoboldAI).": "Запретите токен конца последовательности (EOS) (с помощью KoboldCpp и, возможно, также других токенов с помощью KoboldAI).",
|
||||
"Good for story writing, but should not be used for chat and instruct mode.": "Подходит для написания историй, но не должен использоваться в режиме чата и инструктирования.",
|
||||
|
@ -1772,14 +1813,25 @@
|
|||
"API": "API",
|
||||
"KoboldAI": "KoboldAI",
|
||||
"Use Horde": "Использовать Horde",
|
||||
"API url": "API URL",
|
||||
"API url": "URL-адрес API",
|
||||
"PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Режим обёртки API OpenAI)",
|
||||
"Register a Horde account for faster queue times": "Заведите учетную запись Horde для ускорения генерации",
|
||||
"Learn how to contribute your idle GPU cycles to the Hord": "Узнайте подробнее о том, как использовать время простоя GPU для Hord",
|
||||
"Adjust context size to worker capabilities": "Уточнить размер контекста в соответствии с возможностями рабочих машин",
|
||||
"Adjust response length to worker capabilities": "Уточнить длинну ответа в соответствии с возможностями рабочих машин",
|
||||
"API key": "API-ключ",
|
||||
"Tabby API key": "Tabby API-ключ",
|
||||
"Get it here:": "Получить здесь:",
|
||||
"Register": "Регистрация",
|
||||
"TogetherAI Model": "Модель TogetherAI",
|
||||
"Example: 127.0.0.1:5001": "Пример: http://127.0.0.1:5001",
|
||||
"ggerganov/llama.cpp": "ggerganov/llama.cpp (сервер вывода)",
|
||||
"Example: 127.0.0.1:8080": "Пример: http://127.0.0.1:8080",
|
||||
"Example: 127.0.0.1:11434": "Пример: http://127.0.0.1:11434",
|
||||
"Ollama Model": "Модель Ollama",
|
||||
"Download": "Скачать",
|
||||
"TogetherAI API Key": "TogetherAI API-ключ",
|
||||
"-- Connect to the API --": "-- Подключитесь к API --",
|
||||
"View my Kudos": "Посмотреть мой рейтинг(Kudos)",
|
||||
"Enter": "Вставьте",
|
||||
"to use anonymous mode.": "чтобы использовать анонимный режим.",
|
||||
|
@ -1796,11 +1848,18 @@
|
|||
"Novel AI Model": "Модель NovelAI",
|
||||
"If you are using:": "Если вы используете:",
|
||||
"oobabooga/text-generation-webui": "",
|
||||
"Make sure you run it with": "Убедитесь, что при запуске указали аргумент --extensions openai",
|
||||
"Make sure you run it with": "Убедитесь, что вы запустили его с",
|
||||
"flag": "флажком",
|
||||
"API key (optional)": "Ключ API (опционально)",
|
||||
"Server url": "URL-адрес сервера",
|
||||
"Custom model (optional)": "Пользовательская модель (опционально)",
|
||||
"Bypass API status check": "Обход проверки статуса API",
|
||||
"Mancer AI": "",
|
||||
"Use API key (Only required for Mancer)": "Нажмите на ячейку (и добавьте свой API ключ!):",
|
||||
"Blocking API url": "Блокирующий API url",
|
||||
"Example: http://127.0.0.1:5000/api": "Пример: http://127.0.0.1:5000/api",
|
||||
"Example: 127.0.0.1:5000": "Пример: http://127.0.0.1:5000",
|
||||
"Legacy API (pre-OAI, no streaming)": "Устаревший API (до OAI, без потоковой передачи)",
|
||||
"Bypass status check": "Обход проверки статуса",
|
||||
"Streaming API url": "Потоковый API URL",
|
||||
"Example: ws://127.0.0.1:5005/api/v1/stream": "Пример: ws://127.0.0.1:5005/api/v1/stream",
|
||||
"Mancer API key": "Mancer API ключ",
|
||||
|
@ -1845,7 +1904,6 @@
|
|||
"Chat Start": "Начало чата",
|
||||
"Activation Regex": "Активация Regex",
|
||||
"Instruct Mode": "Режим \"Инструктаж\"",
|
||||
"Enabled": "Включен",
|
||||
"Wrap Sequences with Newline": "Отделять последовательности красной строкой",
|
||||
"Include Names": "Показывать имена",
|
||||
"Force for Groups and Personas": "Усилия для Групп и Персон",
|
||||
|
@ -1859,11 +1917,21 @@
|
|||
"System Sequence Suffix": "Суффикс системной последовательности",
|
||||
"Stop Sequence": "Последовательность остановки",
|
||||
"Context Formatting": "Форматирование контекста",
|
||||
"(Saved to Context Template)": "(Сохраняется в шаблоне контекста)",
|
||||
"Tokenizer": "Токенайзер",
|
||||
"None / Estimated": "Отсутствует/Приблизительно",
|
||||
"Sentencepiece (LLaMA)": "Sentencepiece(LLaMA)",
|
||||
"Sentencepiece (LLaMA)": "Sentencepiece (LLaMA)",
|
||||
"Token Padding": "Заполнение токенов",
|
||||
"Save preset as": "Сохранить предустановку как",
|
||||
"Always add character's name to prompt": "Всегда добавлять имя персонажа в инструкции",
|
||||
"Use as Stop Strings": "Использование в качестве стоп-строк",
|
||||
"Bind to Context": "Привязка к контексту",
|
||||
"Generate only one line per request": "Генерировать только одну строку для каждого запроса",
|
||||
"Misc. Settings": "Доп. настройки",
|
||||
"Auto-Continue": "Авто продолжение",
|
||||
"Collapse Consecutive Newlines": "Свернуть последовательные новые строки",
|
||||
"Allow for Chat Completion APIs": "Разрешить API завершения чата",
|
||||
"Target length (tokens)": "Целевая длина (токены)",
|
||||
"Keep Example Messages in Prompt": "Сохранять примеры сообщений в инструкции",
|
||||
"Remove Empty New Lines from Output": "Удалять пустые строчки из вывода",
|
||||
"Disabled for all models": "Выключено для всех моделей",
|
||||
|
@ -1876,6 +1944,11 @@
|
|||
"Style Anchor": "Стиль Anchors",
|
||||
"World Info": "Информация о мире",
|
||||
"Scan Depth": "Глубина сканирования",
|
||||
"Case-Sensitive": "С учетом регистра",
|
||||
"Match Whole Words": "Сопоставить целые слова",
|
||||
"Use global setting": "Использовать глобальную настройку",
|
||||
"Yes": "Да",
|
||||
"No": "Нет",
|
||||
"Context %": "Процент контекста",
|
||||
"Budget Cap": "Бюджетный лимит",
|
||||
"(0 = disabled)": "(0 = отключено)",
|
||||
|
@ -1892,13 +1965,13 @@
|
|||
"Avatar Style": "Стиль аватаров",
|
||||
"Circle": "Круглые",
|
||||
"Rectangle": "Прямоугольные",
|
||||
"Square": "Квадратные",
|
||||
"Chat Style": "Стиль чата",
|
||||
"Default": "По умолчанию",
|
||||
"Bubbles": "Пузыри",
|
||||
"Chat Width (PC)": "Ширина чата (на PC):",
|
||||
"No Blur Effect": "Отключить эффект размытия",
|
||||
"No Text Shadows": "Отключить тень текста",
|
||||
"Waifu Mode": "!!!РЕЖИМ ВАЙФУ!!!",
|
||||
"No Text Shadows": "Отключить тень от текста",
|
||||
"Waifu Mode": "Рeжим Вайфу",
|
||||
"Message Timer": "Таймер сообщений",
|
||||
"Model Icon": "Показать значки модели",
|
||||
"# of messages (0 = disabled)": "# сообщений (0 = отключено)",
|
||||
|
@ -1906,10 +1979,21 @@
|
|||
"Allow {{char}}: in bot messages": "Показывать {{char}}: в ответах",
|
||||
"Allow {{user}}: in bot messages": "Показать {{user}}: в ответах",
|
||||
"Show tags in responses": "Показывать <теги> в ответах",
|
||||
"Aux List Field": "Вспомогательное поле списка",
|
||||
"Lorebook Import Dialog": "Импрот Lorebook-ка",
|
||||
"MUI Preset": "Предустановка MUI:",
|
||||
"If set in the advanced character definitions, this field will be displayed in the characters list.": "Если это поле задано в расширенных параметрах персонажа, оно будет отображаться в списке персонажа.",
|
||||
"Relaxed API URLS": "Смягченные URL-адреса API",
|
||||
"Custom CSS": "Пользовательский CSS",
|
||||
"Default (oobabooga)": "По умолчанию (oobabooga)",
|
||||
"Mancer Model": "Модель Mancer",
|
||||
"API Type": "Тип API",
|
||||
"Aphrodite API key": "API-ключ Aphrodite",
|
||||
"Relax message trim in Groups": "Расслабленная отделка сообщений в Группах",
|
||||
"Characters Hotswap": "Смена персонажей на лету",
|
||||
"Request token probabilities": "Вероятность запроса токена",
|
||||
"Movable UI Panels": "Перемещение панелей интерфейса",
|
||||
"Reset Panels": "Сбросить панели",
|
||||
"Reset Panels": "Сбросить MovingUI",
|
||||
"UI Colors": "Цвета интерфейса",
|
||||
"Main Text": "Основной текст",
|
||||
"Italics Text": "Курсивный текст",
|
||||
|
@ -1923,6 +2007,8 @@
|
|||
"UI Theme Preset": "Предустановки интерфейса",
|
||||
"Power User Options": "Продвинутые параметры",
|
||||
"Swipes": "Свайвы",
|
||||
"Miscellaneous": "Разное",
|
||||
"Theme Toggles": "Переключатели темы",
|
||||
"Background Sound Only": "Только фоновый звук",
|
||||
"Auto-load Last Chat": "Автоматически загружать последий чат",
|
||||
"Auto-save Message Edits": "Автоматически сохранять отредактированные сообщения",
|
||||
|
@ -1935,6 +2021,15 @@
|
|||
"Automatic (desktop)": "Автоматически (системные настройки)",
|
||||
"Always enabled": "Всегда включена",
|
||||
"Debug Menu": "Меню отладки",
|
||||
"Restore User Input": "Восстановить запрос пользователя",
|
||||
"Character Handling": "Обработка персонажа",
|
||||
"Example Messages Behavior": "Пример поведения в сообщениях:",
|
||||
"Gradual push-out": "Постепенное выталкивание",
|
||||
"Chat/Message Handling": "Обработка чата/сообщения",
|
||||
"Always include examples": "Всегда включать примеры",
|
||||
"Never include examples": "Никогда не включать примеры",
|
||||
"Forbid External Media": "Запрет внешних медиа",
|
||||
"System Backgrounds": "Системные фоны",
|
||||
"Name": "Имя",
|
||||
"Your Avatar": "Ваш Аватар",
|
||||
"Extensions API:": "API для расширений",
|
||||
|
@ -2039,14 +2134,32 @@
|
|||
"Separator": "Разделитель",
|
||||
"Start Reply With": "Начинать ответ с",
|
||||
"Show reply prefix in chat": "Показывать префиксы ответов в чате",
|
||||
"Worlds/Lorebooks": "Миры/Сведения",
|
||||
"Worlds/Lorebooks": "Миры/Lorebook-ки",
|
||||
"Active World(s)": "Активные миры",
|
||||
"Activation Settings": "Настройки активации",
|
||||
"Character Lore Insertion Strategy": "Порядок включения сведений",
|
||||
"Sorted Evenly": "Равномерная сортировка",
|
||||
"Active World(s) for all chats": "Активные миры для всех чатов",
|
||||
"-- World Info not found --": "-- Информация о мире не найдена --",
|
||||
"--- Pick to Edit ---": "Редактировать",
|
||||
"--- Pick to Edit ---": "--- Редактировать ---",
|
||||
"or": "или",
|
||||
"New": "Новый",
|
||||
"Priority": "Приритет",
|
||||
"Custom": "Пользовательский",
|
||||
"Title A-Z": "Название от A до Z",
|
||||
"Title Z-A": "Название от Z до A",
|
||||
"Tokens ↗": "Токены ↗",
|
||||
"Tokens ↘": "Токены ↘",
|
||||
"Depth ↗": "Глубина ↗",
|
||||
"Depth ↘": "Глубина ↘",
|
||||
"Order ↗": "Порядок ↗",
|
||||
"Order ↘": "Порядок ↘",
|
||||
"UID ↗": "Уник. ID ↗",
|
||||
"UID ↘": "Уник. ID ↘",
|
||||
"Trigger% ↗": "Триггер% ↗",
|
||||
"Trigger% ↘": "Триггер% ↘",
|
||||
"Order:": "Порядок:",
|
||||
"Depth:": "Глубина:",
|
||||
"Character Lore First": "Сначала сведения о персонаже",
|
||||
"Global Lore First": "Сначала общие сведения",
|
||||
"Recursive Scan": "Рекурсивное сканирование",
|
||||
|
@ -2058,27 +2171,47 @@
|
|||
"Comma seperated (ignored if empty)": "Разделение запятыми (не используется, если оставлено пустым)",
|
||||
"Use Probability": "Использовать вероятность",
|
||||
"Exclude from recursion": "Исключить из рекурсии",
|
||||
"Entry Title/Memo": "Вставьте Название/Заметку",
|
||||
"Position:": "Положение:",
|
||||
"Before Char Defs": "Перед определением Персонажа",
|
||||
"After Char Defs": "После определения Персонажа",
|
||||
"Before AN": "Перед AN",
|
||||
"After AN": "После AN",
|
||||
"Order:": "Порядок:",
|
||||
"T_Position": "↑Char: Перед определениями Персонажа\n↓Char: После определений Персонажа\n↑AN: Перед Авторскими заметками\n↓AN: После Авторских заметок\n@D: На глубине",
|
||||
"Before Char Defs": "↑Перс.",
|
||||
"After Char Defs": "↓Перс.",
|
||||
"Before AN": "↑АЗ",
|
||||
"After AN": "↓АЗ",
|
||||
"at Depth": "@Г",
|
||||
"Order": "Порядок:",
|
||||
"Probability:": "Вероятность:",
|
||||
"Update a theme file": "Обновить файл темы",
|
||||
"Save as a new theme": "Сохранить как новую тему",
|
||||
"Minimum number of blacklisted words detected to trigger an auto-swipe": "Минимальное количество обнаруженных слов в черном списке для запуска авто-свайпа.",
|
||||
"Delete Entry": "Удалить запись:",
|
||||
"User Message Blur Tint": "Оттенок размытия сообщения пользователя",
|
||||
"AI Message Blur Tint": "Оттенок размытия сообщения ИИ",
|
||||
"User Message Blur Tint": "Сообщение пользователя",
|
||||
"AI Message Blur Tint": "Сообщение ИИ",
|
||||
"Chat Backgrounds": "Фоны чата",
|
||||
"Chat Background": "Фон чата",
|
||||
"UI Background": "Фон интерфейса",
|
||||
"Mad Lab Mode": "Режим безумца",
|
||||
"Show Message Token Count": "Счетчик токенов сообщения",
|
||||
"Compact Input Area (Mobile)": "Компактная зона ввода",
|
||||
"Zen Sliders": "Дзен слайдеры",
|
||||
"UI Border": "Границы интерфейса",
|
||||
"Chat Style:": "Стиль чата",
|
||||
"Chat Width (PC):": "Ширина чата (для ПК)",
|
||||
"Chat Timestamps": "Временные обозначения в чате",
|
||||
"Chat Width (PC)": "Ширина чата (для ПК)",
|
||||
"Chat Timestamps": "Временные метки в чате",
|
||||
"Tags as Folders": "Теги как папки",
|
||||
"Chat Truncation": "Усечение чата",
|
||||
"(0 = unlimited)": "(0 = неограниченное)",
|
||||
"Streaming FPS": "Потоковый FPS",
|
||||
"Gestures": "Жесты",
|
||||
"Message IDs": "ID сообщений",
|
||||
"Prefer Character Card Prompt": "Предпочитать инструкции из Карточки Персонажа",
|
||||
"Prefer Character Card Jailbreak": "Предпочитать JailBreak из Карточки Персонажа",
|
||||
"Press Send to continue": "Нажатие Отправить для продолжения",
|
||||
"Prefer Character Card Jailbreak": "Предпочитать Джеилбреик из Карточки Персонажа",
|
||||
"Press Send to continue": "Нажатие 'Отправить' для продолжения",
|
||||
"Quick 'Continue' button": "Кнопка быстрого 'Продолжения'",
|
||||
"Log prompts to console": "Выводы журнала в консоли",
|
||||
"Never resize avatars": "Никогда не менять размер аватаров",
|
||||
"Show avatar filenames": "Показывать названия файлов аватаров",
|
||||
"Import Card Tags": "Импорт меток Карточки",
|
||||
"Import Card Tags": "Импорт тегов Карточки",
|
||||
"Confirm message deletion": "Подтверждение удаления сообщений",
|
||||
"Spoiler Free Mode": "Режим без спойлеров",
|
||||
"Auto-swipe": "Автоматические свайпы",
|
||||
|
@ -2086,6 +2219,57 @@
|
|||
"Blacklisted words": "Запрещенные слова",
|
||||
"Blacklisted word count to swipe": "Количество запрещенных слов для свайпа",
|
||||
"Reload Chat": "Перезагрузить чат",
|
||||
"Search Settings": "Поиск настроек",
|
||||
"Disabled": "Отключено",
|
||||
"Automatic (PC)": "Автоматическое (ПК)",
|
||||
"Enabled": "Включено",
|
||||
"Simple": "Простой",
|
||||
"Advanced": "Расширенный",
|
||||
"Disables animations and transitions": "Отключение анимаций и переходов.",
|
||||
"removes blur from window backgrounds": "Убрать размытие с фона окон, чтобы ускорить рендеринг.",
|
||||
"Remove text shadow effect": "Удаление эффекта тени от текста.",
|
||||
"Reduce chat height, and put a static sprite behind the chat window": "Уменьшитm высоту чата и поместить статичный спрайт за окном чата.",
|
||||
"Always show the full list of the Message Actions context items for chat messages, instead of hiding them behind '...'": "Всегда показывать полный список контекстных элементов 'Действия с сообщением' для сообщений чата, а не прятать их за '...'.",
|
||||
"Alternative UI for numeric sampling parameters with fewer steps": "Альтернативный пользовательский интерфейс для числовых параметров выборки с меньшим количеством шагов.",
|
||||
"Entirely unrestrict all numeric sampling parameters": "Полностью разграничить все числовые параметры выборки.",
|
||||
"Time the AI's message generation, and show the duration in the chat log": "Время генерации сообщений ИИ и его показ в журнале чата.",
|
||||
"Show a timestamp for each message in the chat log": "Показывать временную метку для каждого сообщения в журнале чата.",
|
||||
"Show an icon for the API that generated the message": "Показать значок API, сгенерировавшего сообщение.",
|
||||
"Show sequential message numbers in the chat log": "Показывать порядковые номера сообщений в журнале чата.",
|
||||
"Show the number of tokens in each message in the chat log": "Показать количество токенов в каждом сообщении в журнале чата.",
|
||||
"Single-row message input area. Mobile only, no effect on PC": "Однорядная область ввода сообщений. Только для мобильных устройств, на ПК не работает.",
|
||||
"In the Character Management panel, show quick selection buttons for favorited characters": "На панели управления персонажами отображають кнопки быстрого выбора для избранных персонажей.",
|
||||
"Show tagged character folders in the character list": "Отобразить теговые папки с персонажами в списке персонажей.",
|
||||
"Play a sound when a message generation finishes": "Воспроизведение звука при завершении генерации сообщения.",
|
||||
"Only play a sound when ST's browser tab is unfocused": "Воспроизводить звук только тогда, когда вкладка браузера ST не выбрана.",
|
||||
"Reduce the formatting requirements on API URLs": "Снижение требований к форматированию URL-адресов API.",
|
||||
"Ask to import the World Info/Lorebook for every new character with embedded lorebook. If unchecked, a brief message will be shown instead": "Запросить импорт информации о мире/Lorebook для каждого нового персонажа со встроенным Lorebook. Если флажок снят, вместо этого будет показано короткое сообщение.",
|
||||
"Restore unsaved user input on page refresh": "Восстановление несохраненного пользовательского запроса при обновлении страницы.",
|
||||
"Allow repositioning certain UI elements by dragging them. PC only, no effect on mobile": "Позволяет изменять положение некоторых элементов пользовательского интерфейса путем их перетаскивания. Только для ПК, на мобильных не работает.",
|
||||
"MovingUI preset. Predefined/saved draggable positions": "Предварительная настройка MovingUI. Предопределенные/сохраненные позиции для перетаскивания.",
|
||||
"Save movingUI changes to a new file": "Сохранение изменений перемещаемого пользовательского интерфейса в новый файл.",
|
||||
"Apply a custom CSS style to all of the ST GUI": "Применить пользовательский стиль CSS ко всем элементам графического интерфейса ST.",
|
||||
"Use fuzzy matching, and search characters in the list by all data fields, not just by a name substring": "Использовать нечеткое сопоставление и искать символы в списке по всем полям данных, а не только по подстроке имени.",
|
||||
"If checked and the character card contains a prompt override (System Prompt), use that instead": "Если установлен флажок и карточка персонажа содержит переопределение подсказки (Системная подсказка), будет использована она вместо изначальной.",
|
||||
"If checked and the character card contains a jailbreak override (Post History Instruction), use that instead": "Если установлен флажок и карточка персонажа содержит переопределение джейлбрейка (инструкция Истории сообщений), будет использована он вместо изначального.",
|
||||
"Avoid cropping and resizing imported character images. When off, crop/resize to 400x600": "Избегать обрезки и изменения размера импортированных изображений персонажей. Если выключено, обрезать/изменить размер до 400x600.",
|
||||
"Show actual file names on the disk, in the characters list display only": "Отображение фактических имен файлов на диске, только в списке персонажей.",
|
||||
"Prompt to import embedded card tags on character import. Otherwise embedded tags are ignored": "Запрос на импорт встроенных тегов карт при импорте персонажей. В противном случае встроенные теги игнорируются.",
|
||||
"Hide character definitions from the editor panel behind a spoiler button": "Скрыть определения персонажей из панели редактора за кнопкой спойлера.",
|
||||
"Show a button in the input area to ask the AI to continue (extend) its last message": "Показать кнопку в области ввода, чтобы попросить ИИ продолжить (продлить) его последнее сообщение.",
|
||||
"Show arrow buttons on the last in-chat message to generate alternative AI responses. Both PC and mobile": "Показывать кнопки со стрелками на последнем сообщении в чате, чтобы генерировать альтернативные ответы ИИ. Как для ПК, так и для мобильных устройств.",
|
||||
"Allow using swiping gestures on the last in-chat message to trigger swipe generation. Mobile only, no effect on PC": "Позволяет использовать жесты смахивания на последнем сообщении в чате, чтобы вызвать альтернативную генерацию. Только для мобильных устройств, на ПК не работает.",
|
||||
"Save edits to messages without confirmation as you type": "Сохранять правки в сообщениях без подтверждения при вводе текста.",
|
||||
"Render LaTeX and AsciiMath equation notation in chat messages. Powered by KaTeX": "Отображение нотации уравнений LaTeX и AsciiMath в сообщениях чата. При поддержке KaTeX.",
|
||||
"Disalow embedded media from other domains in chat messages": "Запретить встроенные медиафайлы из других доменов в сообщениях чата.",
|
||||
"Skip encoding and characters in message text, allowing a subset of HTML markup as well as Markdown": "Не кодировать символы < и > в тексте сообщения, что позволяет использовать подмножество HTML-разметки, а также Markdown.",
|
||||
"Allow AI messages in groups to contain lines spoken by other group members": "Разрешить в групповых сообщениях AI содержать реплики, произнесенные другими членами группы.",
|
||||
"Requests logprobs from the API for the Token Probabilities feature": "Запросить логпробы из API для функции Token Probabilities.",
|
||||
"Automatically reject and re-generate AI message based on configurable criteria": "Автоматическое отклонение и повторная генерация сообщений AI на основе настраиваемых критериев.",
|
||||
"Enable the auto-swipe function. Settings in this section only have an effect when auto-swipe is enabled": "Включить функцию автоматического пролистывания. Настройки в этом разделе действуют только при включенном автопролистывании.",
|
||||
"If the generated message is shorter than this, trigger an auto-swipe": "Если сгенерированное сообщение короче этого значения, срабатывает авто-свайп.",
|
||||
"Reload and redraw the currently open chat": "Перезагрузить и перерисовать открытый в данный момент чат.",
|
||||
"Auto-Expand Message Actions": "Развернуть контекстные элементы",
|
||||
"Not Connected": "Не подключено",
|
||||
"Persona Management": "Управление Персоной",
|
||||
"Persona Description": "Описание Персоны",
|
||||
|
@ -2117,7 +2301,7 @@
|
|||
"Prompt Overrides (For OpenAI/Claude/Scale APIs, Window/OpenRouter, and Instruct mode)": "Замещение инструкций (Для OpenAI/Claude/Scale API, Window/OpenRouter, и Режима Instruct)",
|
||||
"Insert {{original}} into either box to include the respective default prompt from system settings.": "Внесите {{original}} в любое поле для внесения стандартных инструкций из системных настроек",
|
||||
"Main Prompt": "Главные инструкции",
|
||||
"Jailbreak": "JailBreak",
|
||||
"Jailbreak": "Jailbreak",
|
||||
"Creator's Metadata (Not sent with the AI prompt)": "Сведения о создателе (не отправляются ИИ с инструкциями)",
|
||||
"Everything here is optional": "Всё в данных полях опционально",
|
||||
"Created by": "Создано",
|
||||
|
@ -2133,7 +2317,7 @@
|
|||
"Rep. Pen. Freq.": "Частота наказания за повторы",
|
||||
"Rep. Pen. Presence": "Наличие наказания за повторы",
|
||||
"Enter it in the box below:": "Введите в поле ниже:",
|
||||
"separate with commas w/o space between": "разделять запятыми без пробелов между:",
|
||||
"separate with commas w/o space between": "разделять запятыми без пробела",
|
||||
"Document": "Документ",
|
||||
"Suggest replies": "Предлагать ответы",
|
||||
"Show suggested replies. Not all bots support this.": "Показывать предлагаемые ответы. Не все боты поддерживают это.",
|
||||
|
@ -2145,7 +2329,7 @@
|
|||
"AI reply prefix": "Префикс Ответ ИИ",
|
||||
"Custom Stopping Strings": "Настройка ограничивающий нитей",
|
||||
"JSON serialized array of strings": "JSON ориентированный набор нитей",
|
||||
"words you dont want generated separated by comma ','": "слова которые вы не хотите при генерации здесь, разделенные запятой",
|
||||
"words you dont want generated separated by comma ','": "Слова, которые вы не хотите генерировать, разделяются запятыми ','",
|
||||
"Extensions URL": "URL расширений ",
|
||||
"API Key": "Ключ API",
|
||||
"Enter your name": "Введите свое имя",
|
||||
|
@ -2168,9 +2352,14 @@
|
|||
"Injection text (supports parameters)": "Текст включения (Поддерживает параметры)",
|
||||
"Injection depth": "Глубина включения",
|
||||
"Type here...": "Пишите здесь...",
|
||||
"Comma separated (required)": "Разделено запятыми (Обязательно)",
|
||||
"Comma separated (ignored if empty)": "Разделено запятыми (Игнорируется если пусто)",
|
||||
"What this keyword should mean to the AI, sent verbatim": "Значение этого ключевого слова, отправляется ИИ дословно",
|
||||
"Comma separated (required)": "Разделять через запятую (Обязательное)",
|
||||
"Comma separated (ignored if empty)": "Разделять через запятую (Игнорируется если пусто)",
|
||||
"What this keyword should mean to the AI, sent verbatim": "Что это ключевое слово должно означать для ИИ, отправляется дословно",
|
||||
"Filter to Character(s)": "Фильтр к персонажу(ам)",
|
||||
"Character Exclusion": "Исключение персонажей",
|
||||
"Inclusion Group": "Инклюзивная группа",
|
||||
"Only one entry with the same label will be activated": "Будет актив. только одна запись с одинаковой меткой",
|
||||
"-- Characters not found --": "-- Персонаж не найден --",
|
||||
"Not sent to the AI": "Не отправляется ИИ",
|
||||
"(This will be the first message from the character that starts every chat)": "(Это будет первое сообщение от персонажа, когда вы начинаете новый чат)",
|
||||
"Not connected to API!": "Нет подключения к API",
|
||||
|
@ -2178,16 +2367,18 @@
|
|||
"AI Configuration panel will stay open": "Панель Настройки ИИ останется открытой",
|
||||
"Update current preset": "Обновить текущую настройку",
|
||||
"Create new preset": "Создать новую настройку",
|
||||
"Import preset": "Внести настройку",
|
||||
"Export preset": "Скачать настройку",
|
||||
"Delete the preset": "Удалить настройку",
|
||||
"Import preset": "Импорт предустановки",
|
||||
"Export preset": "Экспорт предустановки",
|
||||
"Delete the preset": "Удалить предустановку",
|
||||
"Auto-select this preset for Instruct Mode": "Автоматический выбор этой предустановки для режима 'Инструктаж'.",
|
||||
"Auto-select this preset on API connection": "Автоматический выбор этой предустановки при подключении к API.",
|
||||
"NSFW block goes first in the resulting prompt": "НСФВ блокировка идет первой при отправки Промта",
|
||||
"Enables OpenAI completion streaming": "Включить процесс генерации OpenAI",
|
||||
"Wrap user messages in quotes before sending": "Заключить ответ Пользователя в кавычки",
|
||||
"Restore default prompt": "Восстановить станндартный промт",
|
||||
"New preset": "Новая настройка",
|
||||
"Delete preset": "Удалить настройку",
|
||||
"Restore default jailbreak": "Восстановить стандартный Джейлбрейк",
|
||||
"Restore default jailbreak": "Восстановить стандартный джейлбрейк",
|
||||
"Restore default reply": "Восстановить стандартный ответ",
|
||||
"Restore defaul note": "Восстановить стандартную заметку",
|
||||
"API Connections": "Соединения API",
|
||||
|
@ -2198,7 +2389,6 @@
|
|||
"Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!": "Подверждает ваше соединение к API. Знайте, что за это снимут деньги с вашего счета.",
|
||||
"Create New": "Создать новое",
|
||||
"Edit": "Изменить",
|
||||
"World Info": "Информация о Мире",
|
||||
"Locked = World Editor will stay open": "Закреплено = Редактирование Мира останется открытым",
|
||||
"Entries can activate other entries by mentioning their keywords": "Записи могут активировать другие записи если в них содержаться ключевые слова",
|
||||
"Lookup for the entry keys in the context will respect the case": "Большая буква имеет значение при активации ключевого слова",
|
||||
|
@ -2206,26 +2396,37 @@
|
|||
"Open all Entries": "Открыть все Записи",
|
||||
"Close all Entries": "Закрыть все Записи",
|
||||
"Create": "Создать",
|
||||
"Import World Info": "Внести Информацию Мира",
|
||||
"Export World Info": "Скачать Информацию Мира",
|
||||
"Delete World Info": "Удалить Информацию Мира",
|
||||
"Rename World Info": "Переименовать Информацию Мира",
|
||||
"Import World Info": "Импортировать Мир",
|
||||
"Export World Info": "Экспортировать Мир",
|
||||
"Delete World Info": "Удалить Мир",
|
||||
"Duplicate World Info": "Дублировать Мир",
|
||||
"Rename World Info": "Переименовать Мир",
|
||||
"Refresh": "Обновить",
|
||||
"Primary Keywords": "Основные ключевые слова",
|
||||
"Logic": "Логика",
|
||||
"AND ANY": "И ЛЮБОЙ",
|
||||
"AND ALL": "И ВСЕ",
|
||||
"NOT ALL": "НЕ ВСЕ",
|
||||
"NOT ANY": "НЕ ЛЮБОЙ",
|
||||
"Optional Filter": "Дополнительный фильтр",
|
||||
"New Entry": "Новая Запись",
|
||||
"Fill empty Memo/Titles with Keywords": "Заполните пустые Заметки/Названия ключевыми словами",
|
||||
"Save changes to a new theme file": "Сохранить изменения в новой теме",
|
||||
"removes blur and uses alternative background color for divs": "убирает размытие и использует альтернативный фон для разделов",
|
||||
"If checked and the character card contains a prompt override (System Prompt), use that instead.": "Если выбрано и карточка персонажа содержит собственный промт (Системный Промт), выберите это",
|
||||
"If checked and the character card contains a jailbreak override (Post History Instruction), use that instead.": "Если выбрано и карточка персонажа содержит собственный Джейлбрейк (После Истории Инструкций), выберите это",
|
||||
"AI Response Formatting": "Формат ответа ИИ",
|
||||
"Change Background Image": "Изменить фон",
|
||||
"Extensions": "Расширения",
|
||||
"Click to set a new User Name": "Нажмите что бы выбрать новое имя Личности",
|
||||
"Click to lock your selected persona to the current chat. Click again to remove the lock.": "Нажмите что бы закрепить выьранную личность к текущему чату",
|
||||
"Click to set user name for all messages": "Нажмите что бы закрепить Личность для всех сообщений",
|
||||
"Click to set a new User Name": "Нажмите, чтобы задать новое имя пользователя.",
|
||||
"Click to lock your selected persona to the current chat. Click again to remove the lock.": "Нажмите, чтобы закрепить выбранную персону за текущим чатом. Нажмите еще раз, чтобы снять блокировку.",
|
||||
"Click to set user name for all messages": "Нажмите, чтобы задать имя пользователя для всех сообщений.",
|
||||
"Create a dummy persona": "Создать болванку",
|
||||
"Character Management": "Управление Персонажами",
|
||||
"Locked = Character Management panel will stay open": "Закреплено = Панель Управление Персонажами останется открытой ",
|
||||
"Select/Create Characters": "Выбрать/Создать персонажа",
|
||||
"Token counts may be inaccurate and provided just for reference.": "Счетчик токенов может быть неточным и используется только для примера",
|
||||
"Click to select a new avatar for this character": "Нажмите что бы выбрать новый аватар для этого персонажа",
|
||||
"Example: [{{user}} is a 28-year-old Romanian cat girl.]": "Пример:\n [{{user}} is a 28-year-old Romanian cat girl.]",
|
||||
"Toggle grid view": "Переключить вид сетки",
|
||||
"Add to Favorites": "Добавить в Любимые",
|
||||
"Advanced Definition": "Расширенные Определения",
|
||||
"Character Lore": "Сведения Персонажа",
|
||||
|
@ -2283,13 +2484,22 @@
|
|||
"Add to group": "Добавить в группу",
|
||||
"Add": "Добавить",
|
||||
"Abort request": "Прекратить генерацию",
|
||||
"Send a message": "отправить сообщение",
|
||||
"Ask AI to write your message for you": "ИИ напишет сообщение за вас",
|
||||
"Send a message": "Отправить сообщение",
|
||||
"Ask AI to write your message for you": "Попросить ИИ написать для вас сообщение.",
|
||||
"Continue the last message": "Продолжить текущее сообщение",
|
||||
"Bind user name to that avatar": "Закрепить имя за этой личностью",
|
||||
"Select this as default persona for the new chats.": "Выбрать эту как стартовую личность",
|
||||
"Change persona image": "Сменить изображение личности",
|
||||
"Delete persona": "Удалить личность"
|
||||
"Bind user name to that avatar": "Закрепить имя за этой Персоной",
|
||||
"Select this as default persona for the new chats.": "Выберать эту Персону в качестве персоны по умолчанию для новых чатов.",
|
||||
"Change persona image": "Сменить аватар Персоны.",
|
||||
"Delete persona": "Удалить Персону.",
|
||||
"Reduced Motion": "Сокращение анимаций",
|
||||
"Auto-select": "Авто выбор",
|
||||
"Automatically select a background based on the chat context": "Автоматический выбор фона в зависимости от контекста чата",
|
||||
"Filter": "Фильтр",
|
||||
"Exclude message from prompts": "Исключить сообщение из подсказок",
|
||||
"Include message in prompts": "Включить сообщение в подсказки",
|
||||
"Create checkpoint": "Создание контрольной точки",
|
||||
"Create Branch": "Создать Ветку",
|
||||
"Embed file or image": "Вставить файл или изображение"
|
||||
},
|
||||
"it-it": {
|
||||
"clickslidertips": "consigli per gli slider",
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1606,15 +1606,21 @@ function messageFormatting(mes, ch_name, isSystem, isUser, messageId) {
|
|||
}
|
||||
|
||||
if (!isSystem) {
|
||||
let regexPlacement;
|
||||
if (isUser) {
|
||||
regexPlacement = regex_placement.USER_INPUT;
|
||||
} else if (ch_name !== name2) {
|
||||
regexPlacement = regex_placement.SLASH_COMMAND;
|
||||
} else {
|
||||
regexPlacement = regex_placement.AI_OUTPUT;
|
||||
function getRegexPlacement() {
|
||||
try {
|
||||
if (isUser) {
|
||||
return regex_placement.USER_INPUT;
|
||||
} else if (chat[messageId]?.extra?.type === 'narrator') {
|
||||
return regex_placement.SLASH_COMMAND;
|
||||
} else {
|
||||
return regex_placement.AI_OUTPUT;
|
||||
}
|
||||
} catch {
|
||||
return regex_placement.AI_OUTPUT;
|
||||
}
|
||||
}
|
||||
|
||||
const regexPlacement = getRegexPlacement();
|
||||
const usableMessages = chat.map((x, index) => ({ message: x, index: index })).filter(x => !x.message.is_system);
|
||||
const indexOf = usableMessages.findIndex(x => x.index === Number(messageId));
|
||||
const depth = messageId >= 0 && indexOf !== -1 ? (usableMessages.length - indexOf - 1) : undefined;
|
||||
|
@ -2166,6 +2172,22 @@ function substituteParams(content, _name1, _name2, _original, _group, _replaceCh
|
|||
};
|
||||
}
|
||||
|
||||
const getGroupValue = () => {
|
||||
if (typeof _group === 'string') {
|
||||
return _group;
|
||||
}
|
||||
|
||||
if (selected_group) {
|
||||
const members = groups.find(x => x.id === selected_group)?.members;
|
||||
const names = Array.isArray(members)
|
||||
? members.map(m => characters.find(c => c.avatar === m)?.name).filter(Boolean).join(', ')
|
||||
: '';
|
||||
return names;
|
||||
} else {
|
||||
return _name2 ?? name2;
|
||||
}
|
||||
};
|
||||
|
||||
if (_replaceCharacterCard) {
|
||||
const fields = getCharacterCardFields();
|
||||
environment.charPrompt = fields.system || '';
|
||||
|
@ -2178,10 +2200,9 @@ function substituteParams(content, _name1, _name2, _original, _group, _replaceCh
|
|||
}
|
||||
|
||||
// Must be substituted last so that they're replaced inside {{description}}
|
||||
// TODO: evaluate macros recursively so we don't need to rely on substitution order
|
||||
environment.user = _name1 ?? name1;
|
||||
environment.char = _name2 ?? name2;
|
||||
environment.group = environment.charIfNotGroup = _group ?? name2;
|
||||
environment.group = environment.charIfNotGroup = getGroupValue();
|
||||
environment.model = getGeneratingModel();
|
||||
|
||||
return evaluateMacros(content, environment);
|
||||
|
@ -5902,10 +5923,10 @@ function updateMessage(div) {
|
|||
let regexPlacement;
|
||||
if (mes.is_user) {
|
||||
regexPlacement = regex_placement.USER_INPUT;
|
||||
} else if (mes.name === name2) {
|
||||
regexPlacement = regex_placement.AI_OUTPUT;
|
||||
} else if (mes.name !== name2 || mes.extra?.type === 'narrator') {
|
||||
} else if (mes.extra?.type === 'narrator') {
|
||||
regexPlacement = regex_placement.SLASH_COMMAND;
|
||||
} else {
|
||||
regexPlacement = regex_placement.AI_OUTPUT;
|
||||
}
|
||||
|
||||
// Ignore character override if sent as system
|
||||
|
@ -7751,7 +7772,13 @@ async function connectAPISlash(_, text) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function processDroppedFiles(files) {
|
||||
/**
|
||||
* Imports supported files dropped into the app window.
|
||||
* @param {File[]} files Array of files to process
|
||||
* @param {boolean?} preserveFileNames Whether to preserve original file names
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function processDroppedFiles(files, preserveFileNames = false) {
|
||||
const allowedMimeTypes = [
|
||||
'application/json',
|
||||
'image/png',
|
||||
|
@ -7763,14 +7790,20 @@ export async function processDroppedFiles(files) {
|
|||
|
||||
for (const file of files) {
|
||||
if (allowedMimeTypes.includes(file.type)) {
|
||||
await importCharacter(file);
|
||||
await importCharacter(file, preserveFileNames);
|
||||
} else {
|
||||
toastr.warning('Unsupported file type: ' + file.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function importCharacter(file) {
|
||||
/**
|
||||
* Imports a character from a file.
|
||||
* @param {File} file File to import
|
||||
* @param {boolean?} preserveFileName Whether to preserve original file name
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function importCharacter(file, preserveFileName = false) {
|
||||
const ext = file.name.match(/\.(\w+)$/);
|
||||
if (!ext || !(['json', 'png', 'yaml', 'yml'].includes(ext[1].toLowerCase()))) {
|
||||
return;
|
||||
|
@ -7781,6 +7814,7 @@ async function importCharacter(file) {
|
|||
const formData = new FormData();
|
||||
formData.append('avatar', file);
|
||||
formData.append('file_type', format);
|
||||
formData.append('preserve_file_name', String(preserveFileName));
|
||||
|
||||
const data = await jQuery.ajax({
|
||||
type: 'POST',
|
||||
|
@ -8458,7 +8492,7 @@ jQuery(async function () {
|
|||
throw new Error('Unsuccessful request.');
|
||||
}
|
||||
|
||||
const data = response.json();
|
||||
const data = await response.json();
|
||||
|
||||
if (data.error) {
|
||||
throw new Error('Server returned an error.');
|
||||
|
@ -8470,6 +8504,7 @@ jQuery(async function () {
|
|||
else {
|
||||
if (characters[this_chid].chat == old_filename) {
|
||||
characters[this_chid].chat = newName;
|
||||
$('#selected_chat_pole').val(characters[this_chid].chat);
|
||||
await createOrEditCharacter();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,9 @@ TODO:
|
|||
*/
|
||||
//const DEBUG_TONY_SAMA_FORK_MODE = true
|
||||
|
||||
import { getRequestHeaders, callPopup } from '../../../script.js';
|
||||
import { deleteExtension, extensionNames, installExtension, renderExtensionTemplate } from '../../extensions.js';
|
||||
import { getRequestHeaders, callPopup, processDroppedFiles } from '../../../script.js';
|
||||
import { deleteExtension, extensionNames, getContext, installExtension, renderExtensionTemplate } from '../../extensions.js';
|
||||
import { executeSlashCommands } from '../../slash-commands.js';
|
||||
import { getStringHash, isValidUrl } from '../../utils.js';
|
||||
export { MODULE_NAME };
|
||||
|
||||
|
@ -61,8 +62,8 @@ function downloadAssetsList(url) {
|
|||
for (const i in availableAssets[assetType]) {
|
||||
const asset = availableAssets[assetType][i];
|
||||
const elemId = `assets_install_${assetType}_${i}`;
|
||||
let element = $('<button />', { id: elemId, type: 'button', class: 'asset-download-button menu_button' });
|
||||
const label = $('<i class="fa-fw fa-solid fa-download fa-xl"></i>');
|
||||
let element = $('<div />', { id: elemId, class: 'asset-download-button right_menu_button' });
|
||||
const label = $('<i class="fa-fw fa-solid fa-download fa-lg"></i>');
|
||||
element.append(label);
|
||||
|
||||
//if (DEBUG_TONY_SAMA_FORK_MODE)
|
||||
|
@ -90,6 +91,11 @@ function downloadAssetsList(url) {
|
|||
};
|
||||
|
||||
const assetDelete = async function () {
|
||||
if (assetType === 'character') {
|
||||
toastr.error('Go to the characters menu to delete a character.', 'Character deletion not supported');
|
||||
await executeSlashCommands(`/go ${asset['id']}`);
|
||||
return;
|
||||
}
|
||||
element.off('click');
|
||||
await deleteAsset(assetType, asset['id']);
|
||||
label.removeClass('fa-check');
|
||||
|
@ -126,20 +132,27 @@ function downloadAssetsList(url) {
|
|||
const displayName = DOMPurify.sanitize(asset['name'] || asset['id']);
|
||||
const description = DOMPurify.sanitize(asset['description'] || '');
|
||||
const url = isValidUrl(asset['url']) ? asset['url'] : '';
|
||||
const previewIcon = assetType == 'extension' ? 'fa-arrow-up-right-from-square' : 'fa-headphones-simple';
|
||||
const previewIcon = (assetType === 'extension' || assetType === 'character') ? 'fa-arrow-up-right-from-square' : 'fa-headphones-simple';
|
||||
|
||||
$('<i></i>')
|
||||
const assetBlock = $('<i></i>')
|
||||
.append(element)
|
||||
.append(`<div class="flex-container flexFlowColumn">
|
||||
<span class="flex-container alignitemscenter">
|
||||
.append(`<div class="flex-container flexFlowColumn flexNoGap">
|
||||
<span class="asset-name flex-container alignitemscenter">
|
||||
<b>${displayName}</b>
|
||||
<a class="asset_preview" href="${url}" target="_blank" title="Preview in browser">
|
||||
<i class="fa-solid fa-sm ${previewIcon}"></i>
|
||||
</a>
|
||||
</span>
|
||||
<span>${description}</span>
|
||||
</div>`)
|
||||
.appendTo(assetTypeMenu);
|
||||
<small class="asset-description">
|
||||
${description}
|
||||
</small>
|
||||
</div>`);
|
||||
|
||||
if (assetType === 'character') {
|
||||
assetBlock.find('.asset-name').prepend(`<div class="avatar"><img src="${asset['url']}" alt="${displayName}"></div>`);
|
||||
}
|
||||
|
||||
assetTypeMenu.append(assetBlock);
|
||||
}
|
||||
assetTypeMenu.appendTo('#assets_menu');
|
||||
assetTypeMenu.on('click', 'a.asset_preview', previewAsset);
|
||||
|
@ -186,6 +199,10 @@ function isAssetInstalled(assetType, filename) {
|
|||
assetList = extensionNames.filter(x => x.startsWith(thirdPartyMarker)).map(x => x.replace(thirdPartyMarker, ''));
|
||||
}
|
||||
|
||||
if (assetType == 'character') {
|
||||
assetList = getContext().characters.map(x => x.avatar);
|
||||
}
|
||||
|
||||
for (const i of assetList) {
|
||||
//console.debug(DEBUG_PREFIX,i,filename)
|
||||
if (i.includes(filename))
|
||||
|
@ -215,6 +232,13 @@ async function installAsset(url, assetType, filename) {
|
|||
});
|
||||
if (result.ok) {
|
||||
console.debug(DEBUG_PREFIX, 'Download success.');
|
||||
if (category === 'character') {
|
||||
console.debug(DEBUG_PREFIX, 'Importing character ', filename);
|
||||
const blob = await result.blob();
|
||||
const file = new File([blob], filename, { type: blob.type });
|
||||
await processDroppedFiles([file], true);
|
||||
console.debug(DEBUG_PREFIX, 'Character downloaded.');
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
|
|
|
@ -27,17 +27,14 @@
|
|||
color: inherit;
|
||||
}
|
||||
|
||||
.assets-list-div i {
|
||||
.assets-list-div > i {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: left;
|
||||
padding: 5px;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.assets-list-div i span {
|
||||
margin-left: 10px;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.assets-list-div i span:first-of-type {
|
||||
|
@ -46,12 +43,11 @@
|
|||
|
||||
.asset-download-button {
|
||||
position: relative;
|
||||
width: 50px;
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
outline: none;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
filter: none !important;
|
||||
}
|
||||
|
||||
.asset-download-button:active {
|
||||
|
@ -85,6 +81,21 @@
|
|||
animation: asset-download-button-loading-spinner 1s ease infinite;
|
||||
}
|
||||
|
||||
.asset-name .avatar {
|
||||
--imgSize: 30px !important;
|
||||
flex: unset;
|
||||
width: var(--imgSize);
|
||||
height: var(--imgSize);
|
||||
}
|
||||
|
||||
.asset-name .avatar img {
|
||||
width: var(--imgSize);
|
||||
height: var(--imgSize);
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
object-position: center center;
|
||||
}
|
||||
|
||||
@keyframes asset-download-button-loading-spinner {
|
||||
from {
|
||||
transform: rotate(0turn);
|
||||
|
|
|
@ -5,9 +5,16 @@
|
|||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<div id="open_regex_editor" class="menu_button">
|
||||
<i class="fa-solid fa-pen-to-square"></i>
|
||||
<span>Open Editor</span>
|
||||
<div class="flex-container">
|
||||
<div id="open_regex_editor" class="menu_button">
|
||||
<i class="fa-solid fa-pen-to-square"></i>
|
||||
<span>Open Editor</span>
|
||||
</div>
|
||||
<div id="import_regex" class="menu_button">
|
||||
<i class="fa-solid fa-file-import"></i>
|
||||
<span>Import Script</span>
|
||||
</div>
|
||||
<input type="file" id="import_regex_file" hidden accept="*.json" />
|
||||
</div>
|
||||
<hr />
|
||||
<label>Saved Scripts</label>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { callPopup, getCurrentChatId, reloadCurrentChat, saveSettingsDebounced } from '../../../script.js';
|
||||
import { extension_settings } from '../../extensions.js';
|
||||
import { registerSlashCommand } from '../../slash-commands.js';
|
||||
import { getSortableDelay, uuidv4 } from '../../utils.js';
|
||||
import { download, getFileText, getSortableDelay, uuidv4 } from '../../utils.js';
|
||||
import { resolveVariable } from '../../variables.js';
|
||||
import { regex_placement, runRegexScript } from './engine.js';
|
||||
|
||||
|
@ -93,6 +93,11 @@ async function loadRegexScripts() {
|
|||
scriptHtml.find('.edit_existing_regex').on('click', async function () {
|
||||
await onRegexEditorOpenClick(scriptHtml.attr('id'));
|
||||
});
|
||||
scriptHtml.find('.export_regex').on('click', async function () {
|
||||
const fileName = `${script.scriptName.replace(/[^a-z0-9]/gi, '_').toLowerCase()}.json`;
|
||||
const fileData = JSON.stringify(script, null, 4);
|
||||
download(fileData, fileName, 'application/json');
|
||||
});
|
||||
scriptHtml.find('.delete_regex').on('click', async function () {
|
||||
const confirm = await callPopup('Are you sure you want to delete this regex script?', 'confirm');
|
||||
|
||||
|
@ -270,6 +275,35 @@ function runRegexCallback(args, value) {
|
|||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the import of the regex file.
|
||||
* @param {File} file Input file
|
||||
*/
|
||||
async function onRegexImportFileChange(file) {
|
||||
if (!file) {
|
||||
toastr.error('No file provided.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const fileText = await getFileText(file);
|
||||
const regexScript = JSON.parse(fileText);
|
||||
if (!regexScript.scriptName) {
|
||||
throw new Error('No script name provided.');
|
||||
}
|
||||
|
||||
extension_settings.regex.push(regexScript);
|
||||
|
||||
saveSettingsDebounced();
|
||||
await loadRegexScripts();
|
||||
toastr.success(`Regex script "${regexScript.scriptName}" imported.`);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
toastr.error('Invalid JSON file.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for loading in sequence with other extensions
|
||||
// NOTE: Always puts extension at the top of the list, but this is fine since it's static
|
||||
jQuery(async () => {
|
||||
|
@ -287,6 +321,14 @@ jQuery(async () => {
|
|||
$('#open_regex_editor').on('click', function () {
|
||||
onRegexEditorOpenClick(false);
|
||||
});
|
||||
$('#import_regex_file').on('change', async function () {
|
||||
const inputElement = this instanceof HTMLInputElement && this;
|
||||
await onRegexImportFileChange(inputElement.files[0]);
|
||||
inputElement.value = '';
|
||||
});
|
||||
$('#import_regex').on('click', function () {
|
||||
$('#import_regex_file').trigger('click');
|
||||
});
|
||||
|
||||
$('#saved_regex_scripts').sortable({
|
||||
delay: getSortableDelay(),
|
||||
|
|
|
@ -7,10 +7,13 @@
|
|||
<span class="regex-toggle-on fa-solid fa-toggle-on" title="Disable script"></span>
|
||||
<span class="regex-toggle-off fa-solid fa-toggle-off" title="Enable script"></span>
|
||||
</label>
|
||||
<div class="edit_existing_regex menu_button">
|
||||
<div class="edit_existing_regex menu_button" title="Edit script">
|
||||
<i class="fa-solid fa-pencil"></i>
|
||||
</div>
|
||||
<div class="delete_regex menu_button">
|
||||
<div class="export_regex menu_button" title="Export script">
|
||||
<i class="fa-solid fa-file-export"></i>
|
||||
</div>
|
||||
<div class="delete_regex menu_button" title="Delete script">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -206,6 +206,7 @@ const defaultSettings = {
|
|||
expand: false,
|
||||
interactive_mode: false,
|
||||
multimodal_captioning: false,
|
||||
snap: false,
|
||||
|
||||
prompts: promptTemplates,
|
||||
|
||||
|
@ -389,6 +390,7 @@ async function loadSettings() {
|
|||
$('#sd_openai_quality').val(extension_settings.sd.openai_quality);
|
||||
$('#sd_comfy_url').val(extension_settings.sd.comfy_url);
|
||||
$('#sd_comfy_prompt').val(extension_settings.sd.comfy_prompt);
|
||||
$('#sd_snap').prop('checked', extension_settings.sd.snap);
|
||||
|
||||
for (const style of extension_settings.sd.styles) {
|
||||
const option = document.createElement('option');
|
||||
|
@ -398,23 +400,7 @@ async function loadSettings() {
|
|||
$('#sd_style').append(option);
|
||||
}
|
||||
|
||||
// Find a closest resolution option match for the current width and height
|
||||
let resolutionId = null, minAspectDiff = Infinity, minResolutionDiff = Infinity;
|
||||
for (const [id, resolution] of Object.entries(resolutionOptions)) {
|
||||
const aspectDiff = Math.abs((resolution.width / resolution.height) - (extension_settings.sd.width / extension_settings.sd.height));
|
||||
const resolutionDiff = Math.abs(resolution.width * resolution.height - extension_settings.sd.width * extension_settings.sd.height);
|
||||
|
||||
if (resolutionDiff < minResolutionDiff || (resolutionDiff === minResolutionDiff && aspectDiff < minAspectDiff)) {
|
||||
resolutionId = id;
|
||||
minAspectDiff = aspectDiff;
|
||||
minResolutionDiff = resolutionDiff;
|
||||
}
|
||||
|
||||
if (resolutionDiff === 0 && aspectDiff === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const resolutionId = getClosestKnownResolution();
|
||||
$('#sd_resolution').val(resolutionId);
|
||||
|
||||
toggleSourceControls();
|
||||
|
@ -423,6 +409,32 @@ async function loadSettings() {
|
|||
await loadSettingOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a closest resolution option match for the current width and height.
|
||||
*/
|
||||
function getClosestKnownResolution() {
|
||||
let resolutionId = null;
|
||||
let minTotalDiff = Infinity;
|
||||
|
||||
const targetAspect = extension_settings.sd.width / extension_settings.sd.height;
|
||||
const targetResolution = extension_settings.sd.width * extension_settings.sd.height;
|
||||
|
||||
const diffs = Object.entries(resolutionOptions).map(([id, resolution]) => {
|
||||
const aspectDiff = Math.abs((resolution.width / resolution.height) - targetAspect) / targetAspect;
|
||||
const resolutionDiff = Math.abs(resolution.width * resolution.height - targetResolution) / targetResolution;
|
||||
return { id, totalDiff: aspectDiff + resolutionDiff };
|
||||
});
|
||||
|
||||
for (const { id, totalDiff } of diffs) {
|
||||
if (totalDiff < minTotalDiff) {
|
||||
minTotalDiff = totalDiff;
|
||||
resolutionId = id;
|
||||
}
|
||||
}
|
||||
|
||||
return resolutionId;
|
||||
}
|
||||
|
||||
async function loadSettingOptions() {
|
||||
return Promise.all([
|
||||
loadSamplers(),
|
||||
|
@ -475,6 +487,11 @@ function onMultimodalCaptioningInput() {
|
|||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onSnapInput() {
|
||||
extension_settings.sd.snap = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onStyleSelect() {
|
||||
const selectedStyle = String($('#sd_style').find(':selected').val());
|
||||
const styleObject = extension_settings.sd.styles.find(x => x.name === selectedStyle);
|
||||
|
@ -1659,7 +1676,7 @@ function processReply(str) {
|
|||
str = str.replaceAll('“', '');
|
||||
str = str.replaceAll('.', ',');
|
||||
str = str.replaceAll('\n', ', ');
|
||||
str = str.replace(/[^a-zA-Z0-9,:()]+/g, ' '); // Replace everything except alphanumeric characters and commas with spaces
|
||||
str = str.replace(/[^a-zA-Z0-9,:()']+/g, ' '); // Replace everything except alphanumeric characters and commas with spaces
|
||||
str = str.replace(/\s+/g, ' '); // Collapse multiple whitespaces into one
|
||||
str = str.trim();
|
||||
|
||||
|
@ -1765,7 +1782,7 @@ function setTypeSpecificDimensions(generationType) {
|
|||
const aspectRatio = extension_settings.sd.width / extension_settings.sd.height;
|
||||
|
||||
// Face images are always portrait (pun intended)
|
||||
if (generationType == generationMode.FACE && aspectRatio >= 1) {
|
||||
if ((generationType == generationMode.FACE || generationType == generationMode.FACE_MULTIMODAL) && aspectRatio >= 1) {
|
||||
// Round to nearest multiple of 64
|
||||
extension_settings.sd.height = Math.round(extension_settings.sd.width * 1.5 / 64) * 64;
|
||||
}
|
||||
|
@ -1778,6 +1795,28 @@ function setTypeSpecificDimensions(generationType) {
|
|||
}
|
||||
}
|
||||
|
||||
if (extension_settings.sd.snap) {
|
||||
// Force to use roughly the same pixel count as before rescaling
|
||||
const prevPixelCount = prevSDHeight * prevSDWidth;
|
||||
const newPixelCount = extension_settings.sd.height * extension_settings.sd.width;
|
||||
|
||||
if (prevPixelCount !== newPixelCount) {
|
||||
const ratio = Math.sqrt(prevPixelCount / newPixelCount);
|
||||
extension_settings.sd.height = Math.round(extension_settings.sd.height * ratio / 64) * 64;
|
||||
extension_settings.sd.width = Math.round(extension_settings.sd.width * ratio / 64) * 64;
|
||||
console.log(`Pixel counts after rescaling: ${prevPixelCount} -> ${newPixelCount} (ratio: ${ratio})`);
|
||||
|
||||
const resolution = resolutionOptions[getClosestKnownResolution()];
|
||||
if (resolution) {
|
||||
extension_settings.sd.height = resolution.height;
|
||||
extension_settings.sd.width = resolution.width;
|
||||
console.log('Snap to resolution', JSON.stringify(resolution));
|
||||
} else {
|
||||
console.warn('Snap to resolution failed, using custom dimensions');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { height: prevSDHeight, width: prevSDWidth };
|
||||
}
|
||||
|
||||
|
@ -2349,7 +2388,7 @@ async function onComfyOpenWorkflowEditorClick() {
|
|||
`);
|
||||
$('#sd_comfy_workflow_editor_placeholder_list_custom').append(el);
|
||||
el.find('.sd_comfy_workflow_editor_custom_find').val(placeholder.find);
|
||||
el.find('.sd_comfy_workflow_editor_custom_find').on('input', function() {
|
||||
el.find('.sd_comfy_workflow_editor_custom_find').on('input', function () {
|
||||
placeholder.find = this.value;
|
||||
el.find('.sd_comfy_workflow_editor_custom_final').text(`"%${this.value}%"`);
|
||||
el.attr('data-placeholder', `${this.value}`);
|
||||
|
@ -2357,7 +2396,7 @@ async function onComfyOpenWorkflowEditorClick() {
|
|||
saveSettingsDebounced();
|
||||
});
|
||||
el.find('.sd_comfy_workflow_editor_custom_replace').val(placeholder.replace);
|
||||
el.find('.sd_comfy_workflow_editor_custom_replace').on('input', function() {
|
||||
el.find('.sd_comfy_workflow_editor_custom_replace').on('input', function () {
|
||||
placeholder.replace = this.value;
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
@ -2379,7 +2418,7 @@ async function onComfyOpenWorkflowEditorClick() {
|
|||
addPlaceholderDom(placeholder);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
(extension_settings.sd.comfy_placeholders ?? []).forEach(placeholder=>{
|
||||
(extension_settings.sd.comfy_placeholders ?? []).forEach(placeholder => {
|
||||
addPlaceholderDom(placeholder);
|
||||
});
|
||||
checkPlaceholders();
|
||||
|
@ -2700,6 +2739,7 @@ jQuery(async () => {
|
|||
$('#sd_openai_style').on('change', onOpenAiStyleSelect);
|
||||
$('#sd_openai_quality').on('change', onOpenAiQualitySelect);
|
||||
$('#sd_multimodal_captioning').on('input', onMultimodalCaptioningInput);
|
||||
$('#sd_snap').on('input', onSnapInput);
|
||||
|
||||
$('.sd_settings .inline-drawer-toggle').on('click', function () {
|
||||
initScrollHeight($('#sd_prompt_prefix'));
|
||||
|
|
|
@ -26,6 +26,10 @@
|
|||
<input id="sd_expand" type="checkbox" />
|
||||
Auto-enhance prompts
|
||||
</label>
|
||||
<label for="sd_snap" class="checkbox_label" title="Snap generation requests with a forced aspect ratio (portraits, backgrounds) to the nearest known resolution, while trying to preserve the absolute pixel counts (recommended for SDXL).">
|
||||
<input id="sd_snap" type="checkbox" />
|
||||
Snap auto-adjusted resolutions
|
||||
</label>
|
||||
<label for="sd_source">Source</label>
|
||||
<select id="sd_source">
|
||||
<option value="extras">Extras API (local / remote)</option>
|
||||
|
|
|
@ -196,14 +196,18 @@ class AllTalkTtsProvider {
|
|||
$('#narrator_voice').val(this.settings.narrator_voice_gen);
|
||||
|
||||
console.debug('AllTalkTTS: Settings loaded');
|
||||
await this.initEndpoint();
|
||||
}
|
||||
|
||||
async initEndpoint() {
|
||||
try {
|
||||
// Check if TTS provider is ready
|
||||
this.setupEventListeners();
|
||||
this.updateLanguageDropdown();
|
||||
await this.checkReady();
|
||||
await this.updateSettingsFromServer(); // Fetch dynamic settings from the TTS server
|
||||
await this.fetchTtsVoiceObjects(); // Fetch voices only if service is ready
|
||||
this.updateNarratorVoicesDropdown();
|
||||
this.updateLanguageDropdown();
|
||||
this.setupEventListeners();
|
||||
this.applySettingsToHTML();
|
||||
updateStatus('Ready');
|
||||
} catch (error) {
|
||||
|
@ -488,15 +492,14 @@ class AllTalkTtsProvider {
|
|||
const modelSelect = document.getElementById('switch_model');
|
||||
if (modelSelect) {
|
||||
// Remove the event listener if it was previously added
|
||||
modelSelect.removeEventListener('change', debouncedModelSelectChange);
|
||||
// Add the debounced event listener
|
||||
modelSelect.addEventListener('change', debouncedModelSelectChange);
|
||||
$(modelSelect).off('change').on('change', debouncedModelSelectChange);
|
||||
}
|
||||
|
||||
// DeepSpeed Listener
|
||||
const deepspeedCheckbox = document.getElementById('deepspeed');
|
||||
if (deepspeedCheckbox) {
|
||||
deepspeedCheckbox.addEventListener('change', async (event) => {
|
||||
$(deepspeedCheckbox).off('change').on('change', async (event) => {
|
||||
const deepSpeedValue = event.target.checked ? 'True' : 'False';
|
||||
// Set status to Processing
|
||||
updateStatus('Processing');
|
||||
|
@ -522,7 +525,7 @@ class AllTalkTtsProvider {
|
|||
// Low VRAM Listener
|
||||
const lowVramCheckbox = document.getElementById('low_vram');
|
||||
if (lowVramCheckbox) {
|
||||
lowVramCheckbox.addEventListener('change', async (event) => {
|
||||
$(lowVramCheckbox).off('change').on('change', async (event) => {
|
||||
const lowVramValue = event.target.checked ? 'True' : 'False';
|
||||
// Set status to Processing
|
||||
updateStatus('Processing');
|
||||
|
@ -548,7 +551,7 @@ class AllTalkTtsProvider {
|
|||
// Narrator Voice Dropdown Listener
|
||||
const narratorVoiceSelect = document.getElementById('narrator_voice');
|
||||
if (narratorVoiceSelect) {
|
||||
narratorVoiceSelect.addEventListener('change', (event) => {
|
||||
$(narratorVoiceSelect).off('change').on('change', (event) => {
|
||||
this.settings.narrator_voice_gen = `${event.target.value}.wav`;
|
||||
this.onSettingsChange(); // Save the settings after change
|
||||
});
|
||||
|
@ -556,7 +559,7 @@ class AllTalkTtsProvider {
|
|||
|
||||
const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside');
|
||||
if (textNotInsideSelect) {
|
||||
textNotInsideSelect.addEventListener('change', (event) => {
|
||||
$(textNotInsideSelect).off('change').on('change', (event) => {
|
||||
this.settings.text_not_inside = event.target.value;
|
||||
this.onSettingsChange(); // Save the settings after change
|
||||
});
|
||||
|
@ -569,7 +572,7 @@ class AllTalkTtsProvider {
|
|||
const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues'); // Access the checkbox from index.js
|
||||
|
||||
if (atNarratorSelect && textNotInsideSelect && narratorVoiceSelect) {
|
||||
atNarratorSelect.addEventListener('change', (event) => {
|
||||
$(atNarratorSelect).off('change').on('change', (event) => {
|
||||
const isNarratorEnabled = event.target.value === 'true';
|
||||
this.settings.narrator_enabled = isNarratorEnabled; // Update the setting here
|
||||
textNotInsideSelect.disabled = !isNarratorEnabled;
|
||||
|
@ -605,7 +608,7 @@ class AllTalkTtsProvider {
|
|||
const atGenerationMethodSelect = document.getElementById('at_generation_method');
|
||||
const atNarratorEnabledSelect = document.getElementById('at_narrator_enabled');
|
||||
if (atGenerationMethodSelect) {
|
||||
atGenerationMethodSelect.addEventListener('change', (event) => {
|
||||
$(atGenerationMethodSelect).off('change').on('change', (event) => {
|
||||
const selectedMethod = event.target.value;
|
||||
|
||||
if (selectedMethod === 'streaming_enabled') {
|
||||
|
@ -626,7 +629,7 @@ class AllTalkTtsProvider {
|
|||
// Listener for Language Dropdown
|
||||
const languageSelect = document.getElementById('language_options');
|
||||
if (languageSelect) {
|
||||
languageSelect.addEventListener('change', (event) => {
|
||||
$(languageSelect).off('change').on('change', (event) => {
|
||||
this.settings.language = event.target.value;
|
||||
this.onSettingsChange(); // Save the settings after change
|
||||
});
|
||||
|
@ -635,7 +638,7 @@ class AllTalkTtsProvider {
|
|||
// Listener for AllTalk Endpoint Input
|
||||
const atServerInput = document.getElementById('at_server');
|
||||
if (atServerInput) {
|
||||
atServerInput.addEventListener('input', (event) => {
|
||||
$(atServerInput).off('input').on('input', (event) => {
|
||||
this.settings.provider_endpoint = event.target.value;
|
||||
this.onSettingsChange(); // Save the settings after change
|
||||
});
|
||||
|
@ -665,8 +668,7 @@ class AllTalkTtsProvider {
|
|||
//#########################//
|
||||
|
||||
async onRefreshClick() {
|
||||
await this.checkReady(); // Check if the TTS provider is ready
|
||||
await this.loadSettings(this.settings); // Reload the settings
|
||||
await this.initEndpoint();
|
||||
// Additional actions as needed
|
||||
}
|
||||
|
||||
|
|
|
@ -670,10 +670,9 @@ export function isOpenRouterWithInstruct() {
|
|||
async function populateChatHistory(messages, prompts, chatCompletion, type = null, cyclePrompt = null) {
|
||||
chatCompletion.add(new MessageCollection('chatHistory'), prompts.index('chatHistory'));
|
||||
|
||||
let names = (selected_group && groups.find(x => x.id === selected_group)?.members.map(member => characters.find(c => c.avatar === member)?.name).filter(Boolean).join(', ')) || '';
|
||||
// Reserve budget for new chat message
|
||||
const newChat = selected_group ? oai_settings.new_group_chat_prompt : oai_settings.new_chat_prompt;
|
||||
const newChatMessage = new Message('system', substituteParams(newChat, null, null, null, names), 'newMainChat');
|
||||
const newChatMessage = new Message('system', substituteParams(newChat), 'newMainChat');
|
||||
chatCompletion.reserveBudget(newChatMessage);
|
||||
|
||||
// Reserve budget for group nudge
|
||||
|
@ -2166,8 +2165,13 @@ class ChatCompletion {
|
|||
let squashedMessages = [];
|
||||
|
||||
for (let message of this.messages.collection) {
|
||||
// Force exclude empty messages
|
||||
if (message.role === 'system' && !message.content) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!excludeList.includes(message.identifier) && message.role === 'system' && !message.name) {
|
||||
if (lastMessage && message.content && lastMessage.role === 'system') {
|
||||
if (lastMessage && lastMessage.role === 'system') {
|
||||
lastMessage.content += '\n' + message.content;
|
||||
lastMessage.tokens = tokenHandler.count({ role: lastMessage.role, content: lastMessage.content });
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ import { tags } from './tags.js';
|
|||
import { tokenizers } from './tokenizers.js';
|
||||
import { BIAS_CACHE } from './logit-bias.js';
|
||||
|
||||
import { countOccurrences, debounce, delay, isOdd, resetScrollHeight, shuffle, sortMoments, stringToRange, timestampToMoment } from './utils.js';
|
||||
import { countOccurrences, debounce, delay, download, getFileText, isOdd, resetScrollHeight, shuffle, sortMoments, stringToRange, timestampToMoment } from './utils.js';
|
||||
|
||||
export {
|
||||
loadPowerUserSettings,
|
||||
|
@ -682,6 +682,7 @@ async function CreateZenSliders(elmnt) {
|
|||
sliderID == 'top_k_textgenerationwebui' ||
|
||||
sliderID == 'top_k' ||
|
||||
sliderID == 'rep_pen_slope' ||
|
||||
sliderID == 'smoothing_factor_textgenerationwebui' ||
|
||||
sliderID == 'min_length_textgenerationwebui') {
|
||||
offVal = 0;
|
||||
}
|
||||
|
@ -696,6 +697,9 @@ async function CreateZenSliders(elmnt) {
|
|||
sliderID == 'encoder_rep_pen_textgenerationwebui' ||
|
||||
sliderID == 'temp_textgenerationwebui' ||
|
||||
sliderID == 'temp' ||
|
||||
sliderID == 'min_temp_textgenerationwebui' ||
|
||||
sliderID == 'max_temp_textgenerationwebui' ||
|
||||
sliderID == 'dynatemp_exponent_textgenerationwebui' ||
|
||||
sliderID == 'guidance_scale_textgenerationwebui' ||
|
||||
sliderID == 'guidance_scale') {
|
||||
offVal = 1;
|
||||
|
@ -703,6 +707,9 @@ async function CreateZenSliders(elmnt) {
|
|||
if (sliderID == 'guidance_scale_textgenerationwebui') {
|
||||
numSteps = 78;
|
||||
}
|
||||
if (sliderID == 'top_k_textgenerationwebui') {
|
||||
sliderMin = 0;
|
||||
}
|
||||
//customize amt gen steps
|
||||
if (sliderID !== 'amount_gen' && sliderID !== 'rep_pen_range_textgenerationwebui') {
|
||||
stepScale = sliderRange / numSteps;
|
||||
|
@ -1981,10 +1988,51 @@ async function updateTheme() {
|
|||
toastr.success('Theme saved.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the current theme to a file.
|
||||
*/
|
||||
async function exportTheme() {
|
||||
const themeFile = await saveTheme(power_user.theme);
|
||||
const fileName = `${themeFile.name}.json`;
|
||||
download(JSON.stringify(themeFile, null, 4), fileName, 'application/json');
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports a theme from a file.
|
||||
* @param {File} file File to import.
|
||||
* @returns {Promise<void>} A promise that resolves when the theme is imported.
|
||||
*/
|
||||
async function importTheme(file) {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileText = await getFileText(file);
|
||||
const parsed = JSON.parse(fileText);
|
||||
|
||||
if (!parsed.name) {
|
||||
throw new Error('Missing name');
|
||||
}
|
||||
|
||||
if (themes.some(t => t.name === parsed.name)) {
|
||||
throw new Error('Theme with that name already exists');
|
||||
}
|
||||
|
||||
themes.push(parsed);
|
||||
await applyTheme(parsed.name);
|
||||
await saveTheme(parsed.name);
|
||||
const option = document.createElement('option');
|
||||
option.selected = true;
|
||||
option.value = parsed.name;
|
||||
option.innerText = parsed.name;
|
||||
$('#themes').append(option);
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the current theme to the server.
|
||||
* @param {string|undefined} name Theme name. If undefined, a popup will be shown to enter a name.
|
||||
* @returns {Promise<void>} A promise that resolves when the theme is saved.
|
||||
* @returns {Promise<object>} A promise that resolves when the theme is saved.
|
||||
*/
|
||||
async function saveTheme(name = undefined) {
|
||||
if (typeof name !== 'string') {
|
||||
|
@ -2056,6 +2104,8 @@ async function saveTheme(name = undefined) {
|
|||
power_user.theme = name;
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
return theme;
|
||||
}
|
||||
|
||||
async function saveMovingUI() {
|
||||
|
@ -3278,6 +3328,30 @@ $(document).ready(() => {
|
|||
reloadCurrentChat();
|
||||
});
|
||||
|
||||
$('#ui_preset_import_button').on('click', function () {
|
||||
$('#ui_preset_import_file').trigger('click');
|
||||
});
|
||||
|
||||
$('#ui_preset_import_file').on('change', async function() {
|
||||
const inputElement = this instanceof HTMLInputElement && this;
|
||||
|
||||
try {
|
||||
const file = inputElement?.files?.[0];
|
||||
await importTheme(file);
|
||||
} catch (error) {
|
||||
console.error('Error importing UI theme', error);
|
||||
toastr.error(String(error), 'Failed to import UI theme');
|
||||
} finally {
|
||||
if (inputElement) {
|
||||
inputElement.value = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('#ui_preset_export_button').on('click', async function () {
|
||||
await exportTheme();
|
||||
});
|
||||
|
||||
$(document).on('click', '#debug_table [data-debug-function]', function () {
|
||||
const functionId = $(this).data('debug-function');
|
||||
const functionRecord = debug_functions.find(f => f.functionId === functionId);
|
||||
|
|
|
@ -80,14 +80,23 @@ class SlashCommandParser {
|
|||
const excludedFromRegex = ['sendas'];
|
||||
const firstSpace = text.indexOf(' ');
|
||||
const command = firstSpace !== -1 ? text.substring(1, firstSpace) : text.substring(1);
|
||||
const args = firstSpace !== -1 ? text.substring(firstSpace + 1) : '';
|
||||
let args = firstSpace !== -1 ? text.substring(firstSpace + 1) : '';
|
||||
const argObj = {};
|
||||
let unnamedArg;
|
||||
|
||||
if (args.length > 0) {
|
||||
let match;
|
||||
|
||||
// Match unnamed argument
|
||||
const unnamedArgPattern = /(?:\w+=(?:"(?:\\.|[^"\\])*"|\S+)\s*)*(.*)/s;
|
||||
match = unnamedArgPattern.exec(args);
|
||||
if (match !== null && match[1].length > 0) {
|
||||
args = args.slice(0, -match[1].length);
|
||||
unnamedArg = match[1].trim();
|
||||
}
|
||||
|
||||
// Match named arguments
|
||||
const namedArgPattern = /(\w+)=("(?:\\.|[^"\\])*"|\S+)/g;
|
||||
let match;
|
||||
while ((match = namedArgPattern.exec(args)) !== null) {
|
||||
const key = match[1];
|
||||
const value = match[2];
|
||||
|
@ -95,13 +104,6 @@ class SlashCommandParser {
|
|||
argObj[key] = value.replace(/(^")|("$)/g, '');
|
||||
}
|
||||
|
||||
// Match unnamed argument
|
||||
const unnamedArgPattern = /(?:\w+=(?:"(?:\\.|[^"\\])*"|\S+)\s*)*(.*)/s;
|
||||
match = unnamedArgPattern.exec(args);
|
||||
if (match !== null) {
|
||||
unnamedArg = match[1].trim();
|
||||
}
|
||||
|
||||
// Excluded commands format in their own function
|
||||
if (!excludedFromRegex.includes(command)) {
|
||||
unnamedArg = getRegexedString(
|
||||
|
@ -1119,6 +1121,12 @@ function findCharacterIndex(name) {
|
|||
(a, b) => a.includes(b),
|
||||
];
|
||||
|
||||
const exactAvatarMatch = characters.findIndex(x => x.avatar === name);
|
||||
|
||||
if (exactAvatarMatch !== -1) {
|
||||
return exactAvatarMatch;
|
||||
}
|
||||
|
||||
for (const matchType of matchTypes) {
|
||||
const index = characters.findIndex(x => matchType(x.name.toLowerCase(), name.toLowerCase()));
|
||||
if (index !== -1) {
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
<li><tt>{{mesExamplesRaw}}</tt> – unformatted Dialogue Examples <b>(only for Story String)</b></li>
|
||||
<li><tt>{{user}}</tt> – your current Persona username</li>
|
||||
<li><tt>{{char}}</tt> – the Character's name</li>
|
||||
<li><tt>{{group}}</tt> – a comma-separated list of group member names or the character name in solo chats. Alias: {{charIfNotGroup}}</li>
|
||||
<li><tt>{{model}}</tt> – a text generation model name for the currently selected API. <b>Can be inaccurate!</b></li>
|
||||
<li><tt>{{lastMessage}}</tt> - the text of the latest chat message.</li>
|
||||
<li><tt>{{lastMessageId}}</tt> – index # of the latest chat message. Useful for slash command batching.</li>
|
||||
<li><tt>{{firstIncludedMessageId}}</tt> - the ID of the first message included in the context. Requires generation to be ran at least once in the current session.</li>
|
||||
|
|
|
@ -8,7 +8,7 @@ const { DIRECTORIES, UNSAFE_EXTENSIONS } = require('../constants');
|
|||
const { jsonParser } = require('../express-common');
|
||||
const { clientRelativePath } = require('../util');
|
||||
|
||||
const VALID_CATEGORIES = ['bgm', 'ambient', 'blip', 'live2d', 'vrm'];
|
||||
const VALID_CATEGORIES = ['bgm', 'ambient', 'blip', 'live2d', 'vrm', 'character'];
|
||||
|
||||
/**
|
||||
* Validates the input filename for the asset.
|
||||
|
@ -199,6 +199,13 @@ router.post('/download', jsonParser, async (request, response) => {
|
|||
const fileStream = fs.createWriteStream(destination, { flags: 'wx' });
|
||||
await finished(res.body.pipe(fileStream));
|
||||
|
||||
if (category === 'character') {
|
||||
response.sendFile(temp_path, { root: process.cwd() }, () => {
|
||||
fs.rmSync(temp_path);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Move into asset place
|
||||
console.debug('Download finished, moving file from', temp_path, 'to', file_path);
|
||||
fs.renameSync(temp_path, file_path);
|
||||
|
|
|
@ -513,8 +513,11 @@ router.post('/status', jsonParser, async function (request, response_getstatus_o
|
|||
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.OPENROUTER) {
|
||||
api_url = 'https://openrouter.ai/api/v1';
|
||||
api_key_openai = readSecret(SECRET_KEYS.OPENROUTER);
|
||||
// OpenRouter needs to pass the referer: https://openrouter.ai/docs
|
||||
headers = { 'HTTP-Referer': request.headers.referer };
|
||||
// OpenRouter needs to pass the Referer and X-Title: https://openrouter.ai/docs#requests
|
||||
headers = {
|
||||
'HTTP-Referer': 'https://sillytavern.app',
|
||||
'X-Title': 'SillyTavern',
|
||||
};
|
||||
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.MISTRALAI) {
|
||||
api_url = new URL(request.body.reverse_proxy || API_MISTRAL).toString();
|
||||
api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(SECRET_KEYS.MISTRALAI);
|
||||
|
@ -700,8 +703,11 @@ router.post('/generate', jsonParser, function (request, response) {
|
|||
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.OPENROUTER) {
|
||||
apiUrl = 'https://openrouter.ai/api/v1';
|
||||
apiKey = readSecret(SECRET_KEYS.OPENROUTER);
|
||||
// OpenRouter needs to pass the referer: https://openrouter.ai/docs
|
||||
headers = { 'HTTP-Referer': request.headers.referer };
|
||||
// OpenRouter needs to pass the Referer and X-Title: https://openrouter.ai/docs#requests
|
||||
headers = {
|
||||
'HTTP-Referer': 'https://sillytavern.app',
|
||||
'X-Title': 'SillyTavern',
|
||||
};
|
||||
bodyParams = { 'transforms': ['middle-out'] };
|
||||
|
||||
if (request.body.min_p !== undefined) {
|
||||
|
|
|
@ -796,6 +796,17 @@ function getPngName(file) {
|
|||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the preserved name for the uploaded file if the request is valid.
|
||||
* @param {import("express").Request} request - Express request object
|
||||
* @returns {string | undefined} - The preserved name if the request is valid, otherwise undefined
|
||||
*/
|
||||
function getPreservedName(request) {
|
||||
return request.body.file_type === 'png' && request.body.preserve_file_name === 'true' && request.file?.originalname
|
||||
? path.parse(request.file.originalname).name
|
||||
: undefined;
|
||||
}
|
||||
|
||||
router.post('/import', urlencodedParser, async function (request, response) {
|
||||
if (!request.body || !request.file) return response.sendStatus(400);
|
||||
|
||||
|
@ -803,6 +814,7 @@ router.post('/import', urlencodedParser, async function (request, response) {
|
|||
let filedata = request.file;
|
||||
let uploadPath = path.join(UPLOADS_PATH, filedata.filename);
|
||||
let format = request.body.file_type;
|
||||
const preservedFileName = getPreservedName(request);
|
||||
|
||||
if (format == 'yaml' || format == 'yml') {
|
||||
try {
|
||||
|
@ -894,7 +906,7 @@ router.post('/import', urlencodedParser, async function (request, response) {
|
|||
let jsonData = JSON.parse(img_data);
|
||||
|
||||
jsonData.name = sanitize(jsonData.data?.name || jsonData.name);
|
||||
png_name = getPngName(jsonData.name);
|
||||
png_name = preservedFileName || getPngName(jsonData.name);
|
||||
|
||||
if (jsonData.spec !== undefined) {
|
||||
console.log('Found a v2 character file.');
|
||||
|
|
Loading…
Reference in New Issue