Merge branch 'staging' into slash-fix-bleed

This commit is contained in:
Cohee
2024-02-16 20:48:32 +02:00
26 changed files with 877 additions and 1512 deletions

View File

@ -1,37 +0,0 @@
name: Build and Publish Release (Release)
on:
push:
branches:
- release
jobs:
build_and_publish:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: 18
- name: Install dependencies
run: npm ci
- name: Build and package with pkg
run: |
npm install -g pkg
npm run pkg
- name: Upload binaries to release
uses: softprops/action-gh-release@v1
with:
files: dist/*
tag_name: ci-release
name: Continuous Release (Release)
prerelease: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,37 +0,0 @@
name: Build and Publish Release (Staging)
on:
push:
branches:
- staging
jobs:
build_and_publish:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: 18
- name: Install dependencies
run: npm ci
- name: Build and package with pkg
run: |
npm install -g pkg
npm run pkg
- name: Upload binaries to release
uses: softprops/action-gh-release@v1
with:
files: dist/*
tag_name: ci-staging
name: Continuous Release (Staging)
prerelease: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -4,8 +4,8 @@ name: Create Docker Image on Release
on: on:
release: release:
# Only runs on full releases not pre releases # Allow pre-releases
types: [released] types: [published]
env: env:
# This should allow creation of docker images even in forked repositories # This should allow creation of docker images even in forked repositories

0
.nomedia Normal file
View File

View File

@ -34,7 +34,8 @@ RUN \
rm -f "config.yaml" "public/settings.json" || true && \ rm -f "config.yaml" "public/settings.json" || true && \
ln -s "./config/config.yaml" "config.yaml" || true && \ ln -s "./config/config.yaml" "config.yaml" || true && \
ln -s "../config/settings.json" "public/settings.json" || true && \ ln -s "../config/settings.json" "public/settings.json" || true && \
mkdir "config" || true mkdir "config" || true && \
mkdir -p "public/user" || true
# Cleanup unnecessary files # Cleanup unnecessary files
RUN \ RUN \

1016
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
"dependencies": { "dependencies": {
"@agnai/sentencepiece-js": "^1.1.1", "@agnai/sentencepiece-js": "^1.1.1",
"@agnai/web-tokenizers": "^0.1.3", "@agnai/web-tokenizers": "^0.1.3",
"@dqbd/tiktoken": "^1.0.2", "@dqbd/tiktoken": "^1.0.13",
"bing-translate-api": "^2.9.1", "bing-translate-api": "^2.9.1",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"command-exists": "^1.2.9", "command-exists": "^1.2.9",
@ -55,11 +55,10 @@
"type": "git", "type": "git",
"url": "https://github.com/SillyTavern/SillyTavern.git" "url": "https://github.com/SillyTavern/SillyTavern.git"
}, },
"version": "1.11.3", "version": "1.11.4",
"scripts": { "scripts": {
"start": "node server.js", "start": "node server.js",
"start-multi": "node server.js --disableCsrf", "start-multi": "node server.js --disableCsrf",
"pkg": "pkg --compress Gzip --no-bytecode --public .",
"postinstall": "node post-install.js", "postinstall": "node post-install.js",
"lint": "eslint \"src/**/*.js\" \"public/**/*.js\" ./*.js", "lint": "eslint \"src/**/*.js\" \"public/**/*.js\" ./*.js",
"lint-fix": "eslint \"src/**/*.js\" \"public/**/*.js\" ./*.js --fix" "lint-fix": "eslint \"src/**/*.js\" \"public/**/*.js\" ./*.js --fix"
@ -72,24 +71,8 @@
"no-var": "off" "no-var": "off"
}, },
"main": "server.js", "main": "server.js",
"pkg": {
"targets": [
"node18-linux-x64",
"node18-macos-x64",
"node18-windows-x64"
],
"assets": [
"node_modules/**/*"
],
"outputPath": "dist",
"scripts": [
"server.js"
]
},
"devDependencies": { "devDependencies": {
"eslint": "^8.55.0", "eslint": "^8.55.0",
"jquery": "^3.6.4", "jquery": "^3.6.4"
"pkg": "^5.8.1",
"pkg-fetch": "^3.5.2"
} }
} }

View File

@ -1668,7 +1668,7 @@
"Delete persona": "주인공 삭제하기" "Delete persona": "주인공 삭제하기"
}, },
"ru-ru": { "ru-ru": {
"clickslidertips": "Можно установить вручную, использовав цифру рядом с ползунком", "clickslidertips": "Щелкните на цифру ползунка, чтобы вписать вручную.",
"kobldpresets": "Предустановки Kobold", "kobldpresets": "Предустановки Kobold",
"guikoboldaisettings": "Интерфейс KoboldAI", "guikoboldaisettings": "Интерфейс KoboldAI",
"novelaipreserts": "Предустановки NovelAI", "novelaipreserts": "Предустановки NovelAI",
@ -1680,11 +1680,45 @@
"context size(tokens)": "Размер контекста (в токенах)", "context size(tokens)": "Размер контекста (в токенах)",
"unlocked": "Неограниченный", "unlocked": "Неограниченный",
"Only select models support context sizes greater than 4096 tokens. Increase only if you know what you're doing.": "Только отдельные модели поддерживают контекст, превышающий 4096 токенов. Используйте только если понимаете, что делаете.", "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": "Штраф за повторение",
"rep.pen range": "Диапазон Rep. Pen.", "WI Entry Status:🔵 Constant🟢 Normal❌ Disabled": "Статус входа WI:\n 🔵 Константа\n 🟢 Cтандартный\n ❌ Отключен",
"temperature": "Температура", "rep.pen range": "Диапазон штрафов за повтор.",
"Encoder Rep. Pen.": "Расшифровщик Rep. Pen.", "Temperature controls the randomness in token selection": "Temperature контролирует случайность выбора токенов:\n- низкая Temperature (<1.0) приводит к более предсказуемому тексту, отдавая предпочтение токенам с высокой вероятностью.\n- высокая Temperature (>1.0) повышает креативность и разнообразие вывода, давая токенам с низкой вероятностью больше шансов.\nУстановите значение 1.0 для исходных вероятностей.",
"No Repeat Ngram Size": "No Repeat Ngram Size", "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": "Минимальная длина", "Min Length": "Минимальная длина",
"OpenAI Reverse Proxy": "Прокси с OpenAI", "OpenAI Reverse Proxy": "Прокси с OpenAI",
"Alternative server URL (leave empty to use the default value).": "Альтернативный URL сервера (оставьте пустым для стандартного значения)", "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": "Включите это, если потоковый вывод текста не работает с вашим прокси", "Enable this if the streaming doesn't work with your proxy": "Включите это, если потоковый вывод текста не работает с вашим прокси",
"Context Size (tokens)": "Размер контекста (в токенах)", "Context Size (tokens)": "Размер контекста (в токенах)",
"Max Response Length (tokens)": "Максимальная длина ответа (в токенах)", "Max Response Length (tokens)": "Максимальная длина ответа (в токенах)",
"Temperature": "Температура", "Temperature": "Temperature",
"Frequency Penalty": "Штраф за частоту", "Frequency Penalty": "Штраф за частоту",
"Presence Penalty": "Штраф за присутствие", "Presence Penalty": "Штраф за присутствие",
"Top-p": "Top-p", "Top-p": "Top P",
"Display bot response text chunks as they are generated": "Отображать ответ ИИ по мере генерации текста", "Display bot response text chunks as they are generated": "Отображать ответ ИИ по мере генерации текста",
"Top A": "Top-a", "Top A": "Top А",
"Typical Sampling": "Типичная выборка", "Typical Sampling": "Typical Sampling",
"Tail Free Sampling": "Бесхвостовая выборка", "Tail Free Sampling": "Tail Free Sampling",
"Rep. Pen. Slope": "Rep. Pen. Склон", "Rep. Pen. Slope": "Rep. Pen. Slope",
"Single-line mode": "Режим одной строки", "Single-line mode": "Режим одной строки",
"Top K": "Top-k", "Top K": "Top K",
"Top P": "Top-p", "Top P": "Top P",
"Do Sample": "Сделать образец", "Do Sample": "Сделать образец",
"Add BOS Token": "Добавить BOS-токен", "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 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": "Пропускать специальные токены", "Skip Special Tokens": "Пропускать специальные токены",
@ -1717,12 +1751,13 @@
"Length Penalty": "Штраф за длину", "Length Penalty": "Штраф за длину",
"Early Stopping": "Преждевременная остановка", "Early Stopping": "Преждевременная остановка",
"Contrastive search": "Контрастный поиск", "Contrastive search": "Контрастный поиск",
"Penalty Alpha": "Штраф Альфа", "Penalty Alpha": "Penalty Alpha",
"Seed": "Зерно", "Seed": "Зерно",
"Epsilon Cutoff": "Отсечение эпсилона", "Epsilon Cutoff": "Epsilon Cutoff",
"Eta Cutoff": "Отсечка Eta", "Eta Cutoff": "Eta Cutoff",
"Negative Prompt": "Отрицательная подсказка", "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.": "Добавьте сюда текст, который заставит ИИ генерировать то, что вы не хотите видеть в своих выводах", "Add text here that would make the AI generate things you don't want in your outputs.": "Добавьте сюда текст, который заставит ИИ генерировать то, что вы не хотите видеть в своих выводах",
"Phrase Repetition Penalty": "Штраф за повторение фразы", "Phrase Repetition Penalty": "Штраф за повторение фразы",
"Preamble": "Преамбула", "Preamble": "Преамбула",
@ -1739,8 +1774,14 @@
"NSFW Prioritized": "Предпочитать NSFW", "NSFW Prioritized": "Предпочитать NSFW",
"NSFW prompt text goes first in the prompt to emphasize its effect.": "Отправлять NSFW-инструкцию в начале для усиления его эффекта", "NSFW prompt text goes first in the prompt to emphasize its effect.": "Отправлять NSFW-инструкцию в начале для усиления его эффекта",
"Streaming": "Потоковый вывод текста", "Streaming": "Потоковый вывод текста",
"Display the response bit by bit as it is generated.": "Отображать ответ по кускам в процессе генерации.", "Dynamic Temperature": "Динамическая Temperature",
"When this is off, responses will be displayed all at once when they are complete.": "Если данная функция отключена, ответ будет отображен полностью после генерации.", "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).", "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).", "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.": "Подходит для написания историй, но не должен использоваться в режиме чата и инструктирования.", "Good for story writing, but should not be used for chat and instruct mode.": "Подходит для написания историй, но не должен использоваться в режиме чата и инструктирования.",
@ -1772,14 +1813,25 @@
"API": "API", "API": "API",
"KoboldAI": "KoboldAI", "KoboldAI": "KoboldAI",
"Use Horde": "Использовать Horde", "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 для ускорения генерации", "Register a Horde account for faster queue times": "Заведите учетную запись Horde для ускорения генерации",
"Learn how to contribute your idle GPU cycles to the Hord": "Узнайте подробнее о том, как использовать время простоя GPU для Hord", "Learn how to contribute your idle GPU cycles to the Hord": "Узнайте подробнее о том, как использовать время простоя GPU для Hord",
"Adjust context size to worker capabilities": "Уточнить размер контекста в соответствии с возможностями рабочих машин", "Adjust context size to worker capabilities": "Уточнить размер контекста в соответствии с возможностями рабочих машин",
"Adjust response length to worker capabilities": "Уточнить длинну ответа в соответствии с возможностями рабочих машин", "Adjust response length to worker capabilities": "Уточнить длинну ответа в соответствии с возможностями рабочих машин",
"API key": "API-ключ", "API key": "API-ключ",
"Tabby API key": "Tabby API-ключ",
"Get it here:": "Получить здесь:", "Get it here:": "Получить здесь:",
"Register": "Регистрация", "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)", "View my Kudos": "Посмотреть мой рейтинг(Kudos)",
"Enter": "Вставьте", "Enter": "Вставьте",
"to use anonymous mode.": "чтобы использовать анонимный режим.", "to use anonymous mode.": "чтобы использовать анонимный режим.",
@ -1796,11 +1848,18 @@
"Novel AI Model": "Модель NovelAI", "Novel AI Model": "Модель NovelAI",
"If you are using:": "Если вы используете:", "If you are using:": "Если вы используете:",
"oobabooga/text-generation-webui": "", "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": "", "Mancer AI": "",
"Use API key (Only required for Mancer)": "Нажмите на ячейку (и добавьте свой API ключ!):", "Use API key (Only required for Mancer)": "Нажмите на ячейку (и добавьте свой API ключ!):",
"Blocking API url": "Блокирующий API url", "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", "Streaming API url": "Потоковый API URL",
"Example: ws://127.0.0.1:5005/api/v1/stream": "Пример: ws://127.0.0.1:5005/api/v1/stream", "Example: ws://127.0.0.1:5005/api/v1/stream": "Пример: ws://127.0.0.1:5005/api/v1/stream",
"Mancer API key": "Mancer API ключ", "Mancer API key": "Mancer API ключ",
@ -1845,7 +1904,6 @@
"Chat Start": "Начало чата", "Chat Start": "Начало чата",
"Activation Regex": "Активация Regex", "Activation Regex": "Активация Regex",
"Instruct Mode": "Режим \"Инструктаж\"", "Instruct Mode": "Режим \"Инструктаж\"",
"Enabled": "Включен",
"Wrap Sequences with Newline": "Отделять последовательности красной строкой", "Wrap Sequences with Newline": "Отделять последовательности красной строкой",
"Include Names": "Показывать имена", "Include Names": "Показывать имена",
"Force for Groups and Personas": "Усилия для Групп и Персон", "Force for Groups and Personas": "Усилия для Групп и Персон",
@ -1859,11 +1917,21 @@
"System Sequence Suffix": "Суффикс системной последовательности", "System Sequence Suffix": "Суффикс системной последовательности",
"Stop Sequence": "Последовательность остановки", "Stop Sequence": "Последовательность остановки",
"Context Formatting": "Форматирование контекста", "Context Formatting": "Форматирование контекста",
"(Saved to Context Template)": "(Сохраняется в шаблоне контекста)",
"Tokenizer": "Токенайзер", "Tokenizer": "Токенайзер",
"None / Estimated": "Отсутствует/Приблизительно", "None / Estimated": "Отсутствует/Приблизительно",
"Sentencepiece (LLaMA)": "SentencepieceLLaMA", "Sentencepiece (LLaMA)": "Sentencepiece (LLaMA)",
"Token Padding": "Заполнение токенов", "Token Padding": "Заполнение токенов",
"Save preset as": "Сохранить предустановку как",
"Always add character's name to prompt": "Всегда добавлять имя персонажа в инструкции", "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": "Сохранять примеры сообщений в инструкции", "Keep Example Messages in Prompt": "Сохранять примеры сообщений в инструкции",
"Remove Empty New Lines from Output": "Удалять пустые строчки из вывода", "Remove Empty New Lines from Output": "Удалять пустые строчки из вывода",
"Disabled for all models": "Выключено для всех моделей", "Disabled for all models": "Выключено для всех моделей",
@ -1876,6 +1944,11 @@
"Style Anchor": "Стиль Anchors", "Style Anchor": "Стиль Anchors",
"World Info": "Информация о мире", "World Info": "Информация о мире",
"Scan Depth": "Глубина сканирования", "Scan Depth": "Глубина сканирования",
"Case-Sensitive": "С учетом регистра",
"Match Whole Words": "Сопоставить целые слова",
"Use global setting": "Использовать глобальную настройку",
"Yes": "Да",
"No": "Нет",
"Context %": "Процент контекста", "Context %": "Процент контекста",
"Budget Cap": "Бюджетный лимит", "Budget Cap": "Бюджетный лимит",
"(0 = disabled)": "(0 = отключено)", "(0 = disabled)": "(0 = отключено)",
@ -1892,13 +1965,13 @@
"Avatar Style": "Стиль аватаров", "Avatar Style": "Стиль аватаров",
"Circle": "Круглые", "Circle": "Круглые",
"Rectangle": "Прямоугольные", "Rectangle": "Прямоугольные",
"Square": "Квадратные",
"Chat Style": "Стиль чата", "Chat Style": "Стиль чата",
"Default": "По умолчанию", "Default": "По умолчанию",
"Bubbles": "Пузыри", "Bubbles": "Пузыри",
"Chat Width (PC)": "Ширина чата (на PC",
"No Blur Effect": "Отключить эффект размытия", "No Blur Effect": "Отключить эффект размытия",
"No Text Shadows": "Отключить тень текста", "No Text Shadows": "Отключить тень от текста",
"Waifu Mode": "!!!РЕЖИМ ВАЙФУ!!!", "Waifu Mode": "Рeжим Вайфу",
"Message Timer": "Таймер сообщений", "Message Timer": "Таймер сообщений",
"Model Icon": "Показать значки модели", "Model Icon": "Показать значки модели",
"# of messages (0 = disabled)": "# сообщений (0 = отключено)", "# of messages (0 = disabled)": "# сообщений (0 = отключено)",
@ -1906,10 +1979,21 @@
"Allow {{char}}: in bot messages": "Показывать {{char}}: в ответах", "Allow {{char}}: in bot messages": "Показывать {{char}}: в ответах",
"Allow {{user}}: in bot messages": "Показать {{user}}: в ответах", "Allow {{user}}: in bot messages": "Показать {{user}}: в ответах",
"Show tags in responses": "Показывать <теги> в ответах", "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", "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": "Смена персонажей на лету", "Characters Hotswap": "Смена персонажей на лету",
"Request token probabilities": "Вероятность запроса токена",
"Movable UI Panels": "Перемещение панелей интерфейса", "Movable UI Panels": "Перемещение панелей интерфейса",
"Reset Panels": "Сбросить панели", "Reset Panels": "Сбросить MovingUI",
"UI Colors": "Цвета интерфейса", "UI Colors": "Цвета интерфейса",
"Main Text": "Основной текст", "Main Text": "Основной текст",
"Italics Text": "Курсивный текст", "Italics Text": "Курсивный текст",
@ -1923,6 +2007,8 @@
"UI Theme Preset": "Предустановки интерфейса", "UI Theme Preset": "Предустановки интерфейса",
"Power User Options": "Продвинутые параметры", "Power User Options": "Продвинутые параметры",
"Swipes": "Свайвы", "Swipes": "Свайвы",
"Miscellaneous": "Разное",
"Theme Toggles": "Переключатели темы",
"Background Sound Only": "Только фоновый звук", "Background Sound Only": "Только фоновый звук",
"Auto-load Last Chat": "Автоматически загружать последий чат", "Auto-load Last Chat": "Автоматически загружать последий чат",
"Auto-save Message Edits": "Автоматически сохранять отредактированные сообщения", "Auto-save Message Edits": "Автоматически сохранять отредактированные сообщения",
@ -1935,6 +2021,15 @@
"Automatic (desktop)": "Автоматически (системные настройки)", "Automatic (desktop)": "Автоматически (системные настройки)",
"Always enabled": "Всегда включена", "Always enabled": "Всегда включена",
"Debug Menu": "Меню отладки", "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": "Имя", "Name": "Имя",
"Your Avatar": "Ваш Аватар", "Your Avatar": "Ваш Аватар",
"Extensions API:": "API для расширений", "Extensions API:": "API для расширений",
@ -2039,14 +2134,32 @@
"Separator": "Разделитель", "Separator": "Разделитель",
"Start Reply With": "Начинать ответ с", "Start Reply With": "Начинать ответ с",
"Show reply prefix in chat": "Показывать префиксы ответов в чате", "Show reply prefix in chat": "Показывать префиксы ответов в чате",
"Worlds/Lorebooks": "Миры/Сведения", "Worlds/Lorebooks": "Миры/Lorebook-ки",
"Active World(s)": "Активные миры", "Active World(s)": "Активные миры",
"Activation Settings": "Настройки активации",
"Character Lore Insertion Strategy": "Порядок включения сведений", "Character Lore Insertion Strategy": "Порядок включения сведений",
"Sorted Evenly": "Равномерная сортировка", "Sorted Evenly": "Равномерная сортировка",
"Active World(s) for all chats": "Активные миры для всех чатов", "Active World(s) for all chats": "Активные миры для всех чатов",
"-- World Info not found --": "-- Информация о мире не найдена --", "-- World Info not found --": "-- Информация о мире не найдена --",
"--- Pick to Edit ---": "Редактировать", "--- Pick to Edit ---": "--- Редактировать ---",
"or": "или", "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": "Сначала сведения о персонаже", "Character Lore First": "Сначала сведения о персонаже",
"Global Lore First": "Сначала общие сведения", "Global Lore First": "Сначала общие сведения",
"Recursive Scan": "Рекурсивное сканирование", "Recursive Scan": "Рекурсивное сканирование",
@ -2058,27 +2171,47 @@
"Comma seperated (ignored if empty)": "Разделение запятыми (не используется, если оставлено пустым)", "Comma seperated (ignored if empty)": "Разделение запятыми (не используется, если оставлено пустым)",
"Use Probability": "Использовать вероятность", "Use Probability": "Использовать вероятность",
"Exclude from recursion": "Исключить из рекурсии", "Exclude from recursion": "Исключить из рекурсии",
"Entry Title/Memo": "Вставьте Название/Заметку",
"Position:": "Положение:", "Position:": "Положение:",
"Before Char Defs": "Перед определением Персонажа", "T_Position": "↑Char: Перед определениями Персонажа\n↓Char: После определений Персонажа\n↑AN: Перед Авторскими заметками\n↓AN: После Авторских заметок\n@D: На глубине",
"After Char Defs": "После определения Персонажа", "Before Char Defs": "↑Перс.",
"Before AN": "Перед AN", "After Char Defs": "Перс.",
"After AN": "После AN", "Before AN": "↑АЗ",
"Order:": "Порядок:", "After AN": "↓АЗ",
"at Depth": "@Г",
"Order": "Порядок:",
"Probability:": "Вероятность:", "Probability:": "Вероятность:",
"Update a theme file": "Обновить файл темы",
"Save as a new theme": "Сохранить как новую тему",
"Minimum number of blacklisted words detected to trigger an auto-swipe": "Минимальное количество обнаруженных слов в черном списке для запуска авто-свайпа.",
"Delete Entry": "Удалить запись:", "Delete Entry": "Удалить запись:",
"User Message Blur Tint": "Оттенок размытия сообщения пользователя", "User Message Blur Tint": "Сообщение пользователя",
"AI 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 Style:": "Стиль чата",
"Chat Width (PC):": "Ширина чата (для ПК)", "Chat Width (PC)": "Ширина чата (для ПК)",
"Chat Timestamps": "Временные обозначения в чате", "Chat Timestamps": "Временные метки в чате",
"Tags as Folders": "Теги как папки",
"Chat Truncation": "Усечение чата",
"(0 = unlimited)": "(0 = неограниченное)",
"Streaming FPS": "Потоковый FPS",
"Gestures": "Жесты",
"Message IDs": "ID сообщений", "Message IDs": "ID сообщений",
"Prefer Character Card Prompt": "Предпочитать инструкции из Карточки Персонажа", "Prefer Character Card Prompt": "Предпочитать инструкции из Карточки Персонажа",
"Prefer Character Card Jailbreak": "Предпочитать JailBreak из Карточки Персонажа", "Prefer Character Card Jailbreak": "Предпочитать Джеилбреик из Карточки Персонажа",
"Press Send to continue": "Нажатие Отправить для продолжения", "Press Send to continue": "Нажатие 'Отправить' для продолжения",
"Quick 'Continue' button": "Кнопка быстрого 'Продолжения'",
"Log prompts to console": "Выводы журнала в консоли", "Log prompts to console": "Выводы журнала в консоли",
"Never resize avatars": "Никогда не менять размер аватаров", "Never resize avatars": "Никогда не менять размер аватаров",
"Show avatar filenames": "Показывать названия файлов аватаров", "Show avatar filenames": "Показывать названия файлов аватаров",
"Import Card Tags": "Импорт меток Карточки", "Import Card Tags": "Импорт тегов Карточки",
"Confirm message deletion": "Подтверждение удаления сообщений", "Confirm message deletion": "Подтверждение удаления сообщений",
"Spoiler Free Mode": "Режим без спойлеров", "Spoiler Free Mode": "Режим без спойлеров",
"Auto-swipe": "Автоматические свайпы", "Auto-swipe": "Автоматические свайпы",
@ -2086,6 +2219,57 @@
"Blacklisted words": "Запрещенные слова", "Blacklisted words": "Запрещенные слова",
"Blacklisted word count to swipe": "Количество запрещенных слов для свайпа", "Blacklisted word count to swipe": "Количество запрещенных слов для свайпа",
"Reload Chat": "Перезагрузить чат", "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": "Не подключено", "Not Connected": "Не подключено",
"Persona Management": "Управление Персоной", "Persona Management": "Управление Персоной",
"Persona Description": "Описание Персоны", "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)", "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}} в любое поле для внесения стандартных инструкций из системных настроек", "Insert {{original}} into either box to include the respective default prompt from system settings.": "Внесите {{original}} в любое поле для внесения стандартных инструкций из системных настроек",
"Main Prompt": "Главные инструкции", "Main Prompt": "Главные инструкции",
"Jailbreak": "JailBreak", "Jailbreak": "Jailbreak",
"Creator's Metadata (Not sent with the AI prompt)": "Сведения о создателе (не отправляются ИИ с инструкциями)", "Creator's Metadata (Not sent with the AI prompt)": "Сведения о создателе (не отправляются ИИ с инструкциями)",
"Everything here is optional": "Всё в данных полях опционально", "Everything here is optional": "Всё в данных полях опционально",
"Created by": "Создано", "Created by": "Создано",
@ -2133,7 +2317,7 @@
"Rep. Pen. Freq.": "Частота наказания за повторы", "Rep. Pen. Freq.": "Частота наказания за повторы",
"Rep. Pen. Presence": "Наличие наказания за повторы", "Rep. Pen. Presence": "Наличие наказания за повторы",
"Enter it in the box below:": "Введите в поле ниже:", "Enter it in the box below:": "Введите в поле ниже:",
"separate with commas w/o space between": "разделять запятыми без пробелов между:", "separate with commas w/o space between": "разделять запятыми без пробела",
"Document": "Документ", "Document": "Документ",
"Suggest replies": "Предлагать ответы", "Suggest replies": "Предлагать ответы",
"Show suggested replies. Not all bots support this.": "Показывать предлагаемые ответы. Не все боты поддерживают это.", "Show suggested replies. Not all bots support this.": "Показывать предлагаемые ответы. Не все боты поддерживают это.",
@ -2145,7 +2329,7 @@
"AI reply prefix": "Префикс Ответ ИИ", "AI reply prefix": "Префикс Ответ ИИ",
"Custom Stopping Strings": "Настройка ограничивающий нитей", "Custom Stopping Strings": "Настройка ограничивающий нитей",
"JSON serialized array of strings": "JSON ориентированный набор нитей", "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 расширений ", "Extensions URL": "URL расширений ",
"API Key": "Ключ API", "API Key": "Ключ API",
"Enter your name": "Введите свое имя", "Enter your name": "Введите свое имя",
@ -2168,9 +2352,14 @@
"Injection text (supports parameters)": "Текст включения (Поддерживает параметры)", "Injection text (supports parameters)": "Текст включения (Поддерживает параметры)",
"Injection depth": "Глубина включения", "Injection depth": "Глубина включения",
"Type here...": "Пишите здесь...", "Type here...": "Пишите здесь...",
"Comma separated (required)": "Разделено запятыми (Обязательно)", "Comma separated (required)": "Разделять через запятую (Обязательное)",
"Comma separated (ignored if empty)": "Разделено запятыми (Игнорируется если пусто)", "Comma separated (ignored if empty)": "Разделять через запятую (Игнорируется если пусто)",
"What this keyword should mean to the AI, sent verbatim": "Значение этого ключевого слова, отправляется ИИ дословно", "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": "Не отправляется ИИ", "Not sent to the AI": "Не отправляется ИИ",
"(This will be the first message from the character that starts every chat)": "(Это будет первое сообщение от персонажа, когда вы начинаете новый чат)", "(This will be the first message from the character that starts every chat)": "(Это будет первое сообщение от персонажа, когда вы начинаете новый чат)",
"Not connected to API!": "Нет подключения к API", "Not connected to API!": "Нет подключения к API",
@ -2178,16 +2367,18 @@
"AI Configuration panel will stay open": "Панель Настройки ИИ останется открытой", "AI Configuration panel will stay open": "Панель Настройки ИИ останется открытой",
"Update current preset": "Обновить текущую настройку", "Update current preset": "Обновить текущую настройку",
"Create new preset": "Создать новую настройку", "Create new preset": "Создать новую настройку",
"Import preset": "Внести настройку", "Import preset": "Импорт предустановки",
"Export preset": "Скачать настройку", "Export preset": "Экспорт предустановки",
"Delete the 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": "НСФВ блокировка идет первой при отправки Промта", "NSFW block goes first in the resulting prompt": "НСФВ блокировка идет первой при отправки Промта",
"Enables OpenAI completion streaming": "Включить процесс генерации OpenAI", "Enables OpenAI completion streaming": "Включить процесс генерации OpenAI",
"Wrap user messages in quotes before sending": "Заключить ответ Пользователя в кавычки", "Wrap user messages in quotes before sending": "Заключить ответ Пользователя в кавычки",
"Restore default prompt": "Восстановить станндартный промт", "Restore default prompt": "Восстановить станндартный промт",
"New preset": "Новая настройка", "New preset": "Новая настройка",
"Delete preset": "Удалить настройку", "Delete preset": "Удалить настройку",
"Restore default jailbreak": "Восстановить стандартный Джейлбрейк", "Restore default jailbreak": "Восстановить стандартный джейлбрейк",
"Restore default reply": "Восстановить стандартный ответ", "Restore default reply": "Восстановить стандартный ответ",
"Restore defaul note": "Восстановить стандартную заметку", "Restore defaul note": "Восстановить стандартную заметку",
"API Connections": "Соединения API", "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. Знайте, что за это снимут деньги с вашего счета.", "Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!": "Подверждает ваше соединение к API. Знайте, что за это снимут деньги с вашего счета.",
"Create New": "Создать новое", "Create New": "Создать новое",
"Edit": "Изменить", "Edit": "Изменить",
"World Info": "Информация о Мире",
"Locked = World Editor will stay open": "Закреплено = Редактирование Мира останется открытым", "Locked = World Editor will stay open": "Закреплено = Редактирование Мира останется открытым",
"Entries can activate other entries by mentioning their keywords": "Записи могут активировать другие записи если в них содержаться ключевые слова", "Entries can activate other entries by mentioning their keywords": "Записи могут активировать другие записи если в них содержаться ключевые слова",
"Lookup for the entry keys in the context will respect the case": "Большая буква имеет значение при активации ключевого слова", "Lookup for the entry keys in the context will respect the case": "Большая буква имеет значение при активации ключевого слова",
@ -2206,26 +2396,37 @@
"Open all Entries": "Открыть все Записи", "Open all Entries": "Открыть все Записи",
"Close all Entries": "Закрыть все Записи", "Close all Entries": "Закрыть все Записи",
"Create": "Создать", "Create": "Создать",
"Import World Info": "Внести Информацию Мира", "Import World Info": "Импортировать Мир",
"Export World Info": "Скачать Информацию Мира", "Export World Info": "Экспортировать Мир",
"Delete World Info": "Удалить Информацию Мира", "Delete World Info": "Удалить Мир",
"Rename 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": "Сохранить изменения в новой теме", "Save changes to a new theme file": "Сохранить изменения в новой теме",
"removes blur and uses alternative background color for divs": "убирает размытие и использует альтернативный фон для разделов", "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": "Формат ответа ИИ", "AI Response Formatting": "Формат ответа ИИ",
"Change Background Image": "Изменить фон", "Change Background Image": "Изменить фон",
"Extensions": "Расширения", "Extensions": "Расширения",
"Click to set a new User Name": "Нажмите что бы выбрать новое имя Личности", "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 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 user name for all messages": "Нажмите, чтобы задать имя пользователя для всех сообщений.",
"Create a dummy persona": "Создать болванку", "Create a dummy persona": "Создать болванку",
"Character Management": "Управление Персонажами", "Character Management": "Управление Персонажами",
"Locked = Character Management panel will stay open": "Закреплено = Панель Управление Персонажами останется открытой ", "Locked = Character Management panel will stay open": "Закреплено = Панель Управление Персонажами останется открытой ",
"Select/Create Characters": "Выбрать/Создать персонажа", "Select/Create Characters": "Выбрать/Создать персонажа",
"Token counts may be inaccurate and provided just for reference.": "Счетчик токенов может быть неточным и используется только для примера", "Token counts may be inaccurate and provided just for reference.": "Счетчик токенов может быть неточным и используется только для примера",
"Click to select a new avatar for this character": "Нажмите что бы выбрать новый аватар для этого персонажа", "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": "Добавить в Любимые", "Add to Favorites": "Добавить в Любимые",
"Advanced Definition": "Расширенные Определения", "Advanced Definition": "Расширенные Определения",
"Character Lore": "Сведения Персонажа", "Character Lore": "Сведения Персонажа",
@ -2283,13 +2484,22 @@
"Add to group": "Добавить в группу", "Add to group": "Добавить в группу",
"Add": "Добавить", "Add": "Добавить",
"Abort request": "Прекратить генерацию", "Abort request": "Прекратить генерацию",
"Send a message": "отправить сообщение", "Send a message": "Отправить сообщение",
"Ask AI to write your message for you": "ИИ напишет сообщение за вас", "Ask AI to write your message for you": "Попросить ИИ написать для вас сообщение.",
"Continue the last message": "Продолжить текущее сообщение", "Continue the last message": "Продолжить текущее сообщение",
"Bind user name to that avatar": "Закрепить имя за этой личностью", "Bind user name to that avatar": "Закрепить имя за этой Персоной",
"Select this as default persona for the new chats.": "Выбрать эту как стартовую личность", "Select this as default persona for the new chats.": "Выберать эту Персону в качестве персоны по умолчанию для новых чатов.",
"Change persona image": "Сменить изображение личности", "Change persona image": "Сменить аватар Персоны.",
"Delete persona": "Удалить личность" "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": { "it-it": {
"clickslidertips": "consigli per gli slider", "clickslidertips": "consigli per gli slider",

File diff suppressed because it is too large Load Diff

View File

@ -1603,15 +1603,21 @@ function messageFormatting(mes, ch_name, isSystem, isUser, messageId) {
} }
if (!isSystem) { if (!isSystem) {
let regexPlacement; function getRegexPlacement() {
try {
if (isUser) { if (isUser) {
regexPlacement = regex_placement.USER_INPUT; return regex_placement.USER_INPUT;
} else if (ch_name !== name2) { } else if (chat[messageId]?.extra?.type === 'narrator') {
regexPlacement = regex_placement.SLASH_COMMAND; return regex_placement.SLASH_COMMAND;
} else { } else {
regexPlacement = regex_placement.AI_OUTPUT; 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 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 indexOf = usableMessages.findIndex(x => x.index === Number(messageId));
const depth = messageId >= 0 && indexOf !== -1 ? (usableMessages.length - indexOf - 1) : undefined; const depth = messageId >= 0 && indexOf !== -1 ? (usableMessages.length - indexOf - 1) : undefined;
@ -2163,6 +2169,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) { if (_replaceCharacterCard) {
const fields = getCharacterCardFields(); const fields = getCharacterCardFields();
environment.charPrompt = fields.system || ''; environment.charPrompt = fields.system || '';
@ -2175,10 +2197,9 @@ function substituteParams(content, _name1, _name2, _original, _group, _replaceCh
} }
// Must be substituted last so that they're replaced inside {{description}} // 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.user = _name1 ?? name1;
environment.char = _name2 ?? name2; environment.char = _name2 ?? name2;
environment.group = environment.charIfNotGroup = _group ?? name2; environment.group = environment.charIfNotGroup = getGroupValue();
environment.model = getGeneratingModel(); environment.model = getGeneratingModel();
return evaluateMacros(content, environment); return evaluateMacros(content, environment);
@ -5899,10 +5920,10 @@ function updateMessage(div) {
let regexPlacement; let regexPlacement;
if (mes.is_user) { if (mes.is_user) {
regexPlacement = regex_placement.USER_INPUT; regexPlacement = regex_placement.USER_INPUT;
} else if (mes.name === name2) { } else if (mes.extra?.type === 'narrator') {
regexPlacement = regex_placement.AI_OUTPUT;
} else if (mes.name !== name2 || mes.extra?.type === 'narrator') {
regexPlacement = regex_placement.SLASH_COMMAND; regexPlacement = regex_placement.SLASH_COMMAND;
} else {
regexPlacement = regex_placement.AI_OUTPUT;
} }
// Ignore character override if sent as system // Ignore character override if sent as system
@ -8450,7 +8471,7 @@ jQuery(async function () {
throw new Error('Unsuccessful request.'); throw new Error('Unsuccessful request.');
} }
const data = response.json(); const data = await response.json();
if (data.error) { if (data.error) {
throw new Error('Server returned an error.'); throw new Error('Server returned an error.');
@ -8462,6 +8483,7 @@ jQuery(async function () {
else { else {
if (characters[this_chid].chat == old_filename) { if (characters[this_chid].chat == old_filename) {
characters[this_chid].chat = newName; characters[this_chid].chat = newName;
$('#selected_chat_pole').val(characters[this_chid].chat);
await createOrEditCharacter(); await createOrEditCharacter();
} }
} }

View File

@ -3,8 +3,9 @@ TODO:
*/ */
//const DEBUG_TONY_SAMA_FORK_MODE = true //const DEBUG_TONY_SAMA_FORK_MODE = true
import { getRequestHeaders, callPopup } from '../../../script.js'; import { getRequestHeaders, callPopup, processDroppedFiles } from '../../../script.js';
import { deleteExtension, extensionNames, installExtension, renderExtensionTemplate } from '../../extensions.js'; import { deleteExtension, extensionNames, getContext, installExtension, renderExtensionTemplate } from '../../extensions.js';
import { executeSlashCommands } from '../../slash-commands.js';
import { getStringHash, isValidUrl } from '../../utils.js'; import { getStringHash, isValidUrl } from '../../utils.js';
export { MODULE_NAME }; export { MODULE_NAME };
@ -61,8 +62,8 @@ function downloadAssetsList(url) {
for (const i in availableAssets[assetType]) { for (const i in availableAssets[assetType]) {
const asset = availableAssets[assetType][i]; const asset = availableAssets[assetType][i];
const elemId = `assets_install_${assetType}_${i}`; const elemId = `assets_install_${assetType}_${i}`;
let element = $('<button />', { id: elemId, type: 'button', class: 'asset-download-button menu_button' }); let element = $('<div />', { id: elemId, class: 'asset-download-button right_menu_button' });
const label = $('<i class="fa-fw fa-solid fa-download fa-xl"></i>'); const label = $('<i class="fa-fw fa-solid fa-download fa-lg"></i>');
element.append(label); element.append(label);
//if (DEBUG_TONY_SAMA_FORK_MODE) //if (DEBUG_TONY_SAMA_FORK_MODE)
@ -90,6 +91,11 @@ function downloadAssetsList(url) {
}; };
const assetDelete = async function () { 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'); element.off('click');
await deleteAsset(assetType, asset['id']); await deleteAsset(assetType, asset['id']);
label.removeClass('fa-check'); label.removeClass('fa-check');
@ -126,20 +132,27 @@ function downloadAssetsList(url) {
const displayName = DOMPurify.sanitize(asset['name'] || asset['id']); const displayName = DOMPurify.sanitize(asset['name'] || asset['id']);
const description = DOMPurify.sanitize(asset['description'] || ''); const description = DOMPurify.sanitize(asset['description'] || '');
const url = isValidUrl(asset['url']) ? asset['url'] : ''; 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(element)
.append(`<div class="flex-container flexFlowColumn"> .append(`<div class="flex-container flexFlowColumn flexNoGap">
<span class="flex-container alignitemscenter"> <span class="asset-name flex-container alignitemscenter">
<b>${displayName}</b> <b>${displayName}</b>
<a class="asset_preview" href="${url}" target="_blank" title="Preview in browser"> <a class="asset_preview" href="${url}" target="_blank" title="Preview in browser">
<i class="fa-solid fa-sm ${previewIcon}"></i> <i class="fa-solid fa-sm ${previewIcon}"></i>
</a> </a>
</span> </span>
<span>${description}</span> <small class="asset-description">
</div>`) ${description}
.appendTo(assetTypeMenu); </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.appendTo('#assets_menu');
assetTypeMenu.on('click', 'a.asset_preview', previewAsset); 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, '')); 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) { for (const i of assetList) {
//console.debug(DEBUG_PREFIX,i,filename) //console.debug(DEBUG_PREFIX,i,filename)
if (i.includes(filename)) if (i.includes(filename))
@ -215,6 +232,13 @@ async function installAsset(url, assetType, filename) {
}); });
if (result.ok) { if (result.ok) {
console.debug(DEBUG_PREFIX, 'Download success.'); 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]);
console.debug(DEBUG_PREFIX, 'Character downloaded.');
}
} }
} }
catch (err) { catch (err) {

View File

@ -27,17 +27,14 @@
color: inherit; color: inherit;
} }
.assets-list-div i { .assets-list-div > i {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: left; justify-content: left;
padding: 5px; padding: 5px;
font-style: normal; font-style: normal;
} gap: 5px;
.assets-list-div i span {
margin-left: 10px;
} }
.assets-list-div i span:first-of-type { .assets-list-div i span:first-of-type {
@ -46,12 +43,11 @@
.asset-download-button { .asset-download-button {
position: relative; position: relative;
width: 50px;
padding: 8px 16px;
border: none; border: none;
outline: none; outline: none;
border-radius: 2px; border-radius: 2px;
cursor: pointer; cursor: pointer;
filter: none !important;
} }
.asset-download-button:active { .asset-download-button:active {
@ -85,6 +81,21 @@
animation: asset-download-button-loading-spinner 1s ease infinite; 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 { @keyframes asset-download-button-loading-spinner {
from { from {
transform: rotate(0turn); transform: rotate(0turn);

View File

@ -5,10 +5,17 @@
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div> <div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div> </div>
<div class="inline-drawer-content"> <div class="inline-drawer-content">
<div class="flex-container">
<div id="open_regex_editor" class="menu_button"> <div id="open_regex_editor" class="menu_button">
<i class="fa-solid fa-pen-to-square"></i> <i class="fa-solid fa-pen-to-square"></i>
<span>Open Editor</span> <span>Open Editor</span>
</div> </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 /> <hr />
<label>Saved Scripts</label> <label>Saved Scripts</label>
<div id="saved_regex_scripts" class="flex-container regex-script-container flexFlowColumn"></div> <div id="saved_regex_scripts" class="flex-container regex-script-container flexFlowColumn"></div>

View File

@ -1,7 +1,7 @@
import { callPopup, getCurrentChatId, reloadCurrentChat, saveSettingsDebounced } from '../../../script.js'; import { callPopup, getCurrentChatId, reloadCurrentChat, saveSettingsDebounced } from '../../../script.js';
import { extension_settings } from '../../extensions.js'; import { extension_settings } from '../../extensions.js';
import { registerSlashCommand } from '../../slash-commands.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 { resolveVariable } from '../../variables.js';
import { regex_placement, runRegexScript } from './engine.js'; import { regex_placement, runRegexScript } from './engine.js';
@ -93,6 +93,11 @@ async function loadRegexScripts() {
scriptHtml.find('.edit_existing_regex').on('click', async function () { scriptHtml.find('.edit_existing_regex').on('click', async function () {
await onRegexEditorOpenClick(scriptHtml.attr('id')); 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 () { scriptHtml.find('.delete_regex').on('click', async function () {
const confirm = await callPopup('Are you sure you want to delete this regex script?', 'confirm'); 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; 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 // 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 // NOTE: Always puts extension at the top of the list, but this is fine since it's static
jQuery(async () => { jQuery(async () => {
@ -287,6 +321,14 @@ jQuery(async () => {
$('#open_regex_editor').on('click', function () { $('#open_regex_editor').on('click', function () {
onRegexEditorOpenClick(false); 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({ $('#saved_regex_scripts').sortable({
delay: getSortableDelay(), delay: getSortableDelay(),

View File

@ -7,10 +7,13 @@
<span class="regex-toggle-on fa-solid fa-toggle-on" title="Disable script"></span> <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> <span class="regex-toggle-off fa-solid fa-toggle-off" title="Enable script"></span>
</label> </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> <i class="fa-solid fa-pencil"></i>
</div> </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> <i class="fa-solid fa-trash"></i>
</div> </div>
</div> </div>

View File

@ -206,6 +206,7 @@ const defaultSettings = {
expand: false, expand: false,
interactive_mode: false, interactive_mode: false,
multimodal_captioning: false, multimodal_captioning: false,
snap: false,
prompts: promptTemplates, prompts: promptTemplates,
@ -389,6 +390,7 @@ async function loadSettings() {
$('#sd_openai_quality').val(extension_settings.sd.openai_quality); $('#sd_openai_quality').val(extension_settings.sd.openai_quality);
$('#sd_comfy_url').val(extension_settings.sd.comfy_url); $('#sd_comfy_url').val(extension_settings.sd.comfy_url);
$('#sd_comfy_prompt').val(extension_settings.sd.comfy_prompt); $('#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) { for (const style of extension_settings.sd.styles) {
const option = document.createElement('option'); const option = document.createElement('option');
@ -398,23 +400,7 @@ async function loadSettings() {
$('#sd_style').append(option); $('#sd_style').append(option);
} }
// Find a closest resolution option match for the current width and height const resolutionId = getClosestKnownResolution();
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;
}
}
$('#sd_resolution').val(resolutionId); $('#sd_resolution').val(resolutionId);
toggleSourceControls(); toggleSourceControls();
@ -423,6 +409,32 @@ async function loadSettings() {
await loadSettingOptions(); 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() { async function loadSettingOptions() {
return Promise.all([ return Promise.all([
loadSamplers(), loadSamplers(),
@ -475,6 +487,11 @@ function onMultimodalCaptioningInput() {
saveSettingsDebounced(); saveSettingsDebounced();
} }
function onSnapInput() {
extension_settings.sd.snap = !!$(this).prop('checked');
saveSettingsDebounced();
}
function onStyleSelect() { function onStyleSelect() {
const selectedStyle = String($('#sd_style').find(':selected').val()); const selectedStyle = String($('#sd_style').find(':selected').val());
const styleObject = extension_settings.sd.styles.find(x => x.name === selectedStyle); 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('.', ','); str = str.replaceAll('.', ',');
str = str.replaceAll('\n', ', '); 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.replace(/\s+/g, ' '); // Collapse multiple whitespaces into one
str = str.trim(); str = str.trim();
@ -1765,7 +1782,7 @@ function setTypeSpecificDimensions(generationType) {
const aspectRatio = extension_settings.sd.width / extension_settings.sd.height; const aspectRatio = extension_settings.sd.width / extension_settings.sd.height;
// Face images are always portrait (pun intended) // 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 // Round to nearest multiple of 64
extension_settings.sd.height = Math.round(extension_settings.sd.width * 1.5 / 64) * 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 }; return { height: prevSDHeight, width: prevSDWidth };
} }
@ -2349,7 +2388,7 @@ async function onComfyOpenWorkflowEditorClick() {
`); `);
$('#sd_comfy_workflow_editor_placeholder_list_custom').append(el); $('#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').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; placeholder.find = this.value;
el.find('.sd_comfy_workflow_editor_custom_final').text(`"%${this.value}%"`); el.find('.sd_comfy_workflow_editor_custom_final').text(`"%${this.value}%"`);
el.attr('data-placeholder', `${this.value}`); el.attr('data-placeholder', `${this.value}`);
@ -2357,7 +2396,7 @@ async function onComfyOpenWorkflowEditorClick() {
saveSettingsDebounced(); saveSettingsDebounced();
}); });
el.find('.sd_comfy_workflow_editor_custom_replace').val(placeholder.replace); 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; placeholder.replace = this.value;
saveSettingsDebounced(); saveSettingsDebounced();
}); });
@ -2379,7 +2418,7 @@ async function onComfyOpenWorkflowEditorClick() {
addPlaceholderDom(placeholder); addPlaceholderDom(placeholder);
saveSettingsDebounced(); saveSettingsDebounced();
}); });
(extension_settings.sd.comfy_placeholders ?? []).forEach(placeholder=>{ (extension_settings.sd.comfy_placeholders ?? []).forEach(placeholder => {
addPlaceholderDom(placeholder); addPlaceholderDom(placeholder);
}); });
checkPlaceholders(); checkPlaceholders();
@ -2700,6 +2739,7 @@ jQuery(async () => {
$('#sd_openai_style').on('change', onOpenAiStyleSelect); $('#sd_openai_style').on('change', onOpenAiStyleSelect);
$('#sd_openai_quality').on('change', onOpenAiQualitySelect); $('#sd_openai_quality').on('change', onOpenAiQualitySelect);
$('#sd_multimodal_captioning').on('input', onMultimodalCaptioningInput); $('#sd_multimodal_captioning').on('input', onMultimodalCaptioningInput);
$('#sd_snap').on('input', onSnapInput);
$('.sd_settings .inline-drawer-toggle').on('click', function () { $('.sd_settings .inline-drawer-toggle').on('click', function () {
initScrollHeight($('#sd_prompt_prefix')); initScrollHeight($('#sd_prompt_prefix'));

View File

@ -26,6 +26,10 @@
<input id="sd_expand" type="checkbox" /> <input id="sd_expand" type="checkbox" />
Auto-enhance prompts Auto-enhance prompts
</label> </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> <label for="sd_source">Source</label>
<select id="sd_source"> <select id="sd_source">
<option value="extras">Extras API (local / remote)</option> <option value="extras">Extras API (local / remote)</option>

View File

@ -196,14 +196,18 @@ class AllTalkTtsProvider {
$('#narrator_voice').val(this.settings.narrator_voice_gen); $('#narrator_voice').val(this.settings.narrator_voice_gen);
console.debug('AllTalkTTS: Settings loaded'); console.debug('AllTalkTTS: Settings loaded');
await this.initEndpoint();
}
async initEndpoint() {
try { try {
// Check if TTS provider is ready // Check if TTS provider is ready
this.setupEventListeners();
this.updateLanguageDropdown();
await this.checkReady(); await this.checkReady();
await this.updateSettingsFromServer(); // Fetch dynamic settings from the TTS server await this.updateSettingsFromServer(); // Fetch dynamic settings from the TTS server
await this.fetchTtsVoiceObjects(); // Fetch voices only if service is ready await this.fetchTtsVoiceObjects(); // Fetch voices only if service is ready
this.updateNarratorVoicesDropdown(); this.updateNarratorVoicesDropdown();
this.updateLanguageDropdown();
this.setupEventListeners();
this.applySettingsToHTML(); this.applySettingsToHTML();
updateStatus('Ready'); updateStatus('Ready');
} catch (error) { } catch (error) {
@ -488,15 +492,14 @@ class AllTalkTtsProvider {
const modelSelect = document.getElementById('switch_model'); const modelSelect = document.getElementById('switch_model');
if (modelSelect) { if (modelSelect) {
// Remove the event listener if it was previously added // Remove the event listener if it was previously added
modelSelect.removeEventListener('change', debouncedModelSelectChange);
// Add the debounced event listener // Add the debounced event listener
modelSelect.addEventListener('change', debouncedModelSelectChange); $(modelSelect).off('change').on('change', debouncedModelSelectChange);
} }
// DeepSpeed Listener // DeepSpeed Listener
const deepspeedCheckbox = document.getElementById('deepspeed'); const deepspeedCheckbox = document.getElementById('deepspeed');
if (deepspeedCheckbox) { if (deepspeedCheckbox) {
deepspeedCheckbox.addEventListener('change', async (event) => { $(deepspeedCheckbox).off('change').on('change', async (event) => {
const deepSpeedValue = event.target.checked ? 'True' : 'False'; const deepSpeedValue = event.target.checked ? 'True' : 'False';
// Set status to Processing // Set status to Processing
updateStatus('Processing'); updateStatus('Processing');
@ -522,7 +525,7 @@ class AllTalkTtsProvider {
// Low VRAM Listener // Low VRAM Listener
const lowVramCheckbox = document.getElementById('low_vram'); const lowVramCheckbox = document.getElementById('low_vram');
if (lowVramCheckbox) { if (lowVramCheckbox) {
lowVramCheckbox.addEventListener('change', async (event) => { $(lowVramCheckbox).off('change').on('change', async (event) => {
const lowVramValue = event.target.checked ? 'True' : 'False'; const lowVramValue = event.target.checked ? 'True' : 'False';
// Set status to Processing // Set status to Processing
updateStatus('Processing'); updateStatus('Processing');
@ -548,7 +551,7 @@ class AllTalkTtsProvider {
// Narrator Voice Dropdown Listener // Narrator Voice Dropdown Listener
const narratorVoiceSelect = document.getElementById('narrator_voice'); const narratorVoiceSelect = document.getElementById('narrator_voice');
if (narratorVoiceSelect) { if (narratorVoiceSelect) {
narratorVoiceSelect.addEventListener('change', (event) => { $(narratorVoiceSelect).off('change').on('change', (event) => {
this.settings.narrator_voice_gen = `${event.target.value}.wav`; this.settings.narrator_voice_gen = `${event.target.value}.wav`;
this.onSettingsChange(); // Save the settings after change this.onSettingsChange(); // Save the settings after change
}); });
@ -556,7 +559,7 @@ class AllTalkTtsProvider {
const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside'); const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside');
if (textNotInsideSelect) { if (textNotInsideSelect) {
textNotInsideSelect.addEventListener('change', (event) => { $(textNotInsideSelect).off('change').on('change', (event) => {
this.settings.text_not_inside = event.target.value; this.settings.text_not_inside = event.target.value;
this.onSettingsChange(); // Save the settings after change 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 const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues'); // Access the checkbox from index.js
if (atNarratorSelect && textNotInsideSelect && narratorVoiceSelect) { if (atNarratorSelect && textNotInsideSelect && narratorVoiceSelect) {
atNarratorSelect.addEventListener('change', (event) => { $(atNarratorSelect).off('change').on('change', (event) => {
const isNarratorEnabled = event.target.value === 'true'; const isNarratorEnabled = event.target.value === 'true';
this.settings.narrator_enabled = isNarratorEnabled; // Update the setting here this.settings.narrator_enabled = isNarratorEnabled; // Update the setting here
textNotInsideSelect.disabled = !isNarratorEnabled; textNotInsideSelect.disabled = !isNarratorEnabled;
@ -605,7 +608,7 @@ class AllTalkTtsProvider {
const atGenerationMethodSelect = document.getElementById('at_generation_method'); const atGenerationMethodSelect = document.getElementById('at_generation_method');
const atNarratorEnabledSelect = document.getElementById('at_narrator_enabled'); const atNarratorEnabledSelect = document.getElementById('at_narrator_enabled');
if (atGenerationMethodSelect) { if (atGenerationMethodSelect) {
atGenerationMethodSelect.addEventListener('change', (event) => { $(atGenerationMethodSelect).off('change').on('change', (event) => {
const selectedMethod = event.target.value; const selectedMethod = event.target.value;
if (selectedMethod === 'streaming_enabled') { if (selectedMethod === 'streaming_enabled') {
@ -626,7 +629,7 @@ class AllTalkTtsProvider {
// Listener for Language Dropdown // Listener for Language Dropdown
const languageSelect = document.getElementById('language_options'); const languageSelect = document.getElementById('language_options');
if (languageSelect) { if (languageSelect) {
languageSelect.addEventListener('change', (event) => { $(languageSelect).off('change').on('change', (event) => {
this.settings.language = event.target.value; this.settings.language = event.target.value;
this.onSettingsChange(); // Save the settings after change this.onSettingsChange(); // Save the settings after change
}); });
@ -635,7 +638,7 @@ class AllTalkTtsProvider {
// Listener for AllTalk Endpoint Input // Listener for AllTalk Endpoint Input
const atServerInput = document.getElementById('at_server'); const atServerInput = document.getElementById('at_server');
if (atServerInput) { if (atServerInput) {
atServerInput.addEventListener('input', (event) => { $(atServerInput).off('input').on('input', (event) => {
this.settings.provider_endpoint = event.target.value; this.settings.provider_endpoint = event.target.value;
this.onSettingsChange(); // Save the settings after change this.onSettingsChange(); // Save the settings after change
}); });
@ -665,8 +668,7 @@ class AllTalkTtsProvider {
//#########################// //#########################//
async onRefreshClick() { async onRefreshClick() {
await this.checkReady(); // Check if the TTS provider is ready await this.initEndpoint();
await this.loadSettings(this.settings); // Reload the settings
// Additional actions as needed // Additional actions as needed
} }

View File

@ -670,10 +670,9 @@ export function isOpenRouterWithInstruct() {
async function populateChatHistory(messages, prompts, chatCompletion, type = null, cyclePrompt = null) { async function populateChatHistory(messages, prompts, chatCompletion, type = null, cyclePrompt = null) {
chatCompletion.add(new MessageCollection('chatHistory'), prompts.index('chatHistory')); 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 // Reserve budget for new chat message
const newChat = selected_group ? oai_settings.new_group_chat_prompt : oai_settings.new_chat_prompt; 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); chatCompletion.reserveBudget(newChatMessage);
// Reserve budget for group nudge // Reserve budget for group nudge
@ -1692,24 +1691,17 @@ async function sendOpenAIRequest(type, messages, signal) {
throw new Error(`Got response status ${response.status}`); throw new Error(`Got response status ${response.status}`);
} }
if (stream) { if (stream) {
let reader;
let isSSEStream = oai_settings.chat_completion_source !== chat_completion_sources.MAKERSUITE;
if (isSSEStream) {
const eventStream = new EventSourceStream(); const eventStream = new EventSourceStream();
response.body.pipeThrough(eventStream); response.body.pipeThrough(eventStream);
reader = eventStream.readable.getReader(); const reader = eventStream.readable.getReader();
} else {
reader = response.body.getReader();
}
return async function* streamData() { return async function* streamData() {
let text = ''; let text = '';
let utf8Decoder = new TextDecoder();
const swipes = []; const swipes = [];
while (true) { while (true) {
const { done, value } = await reader.read(); const { done, value } = await reader.read();
if (done) return; if (done) return;
const rawData = isSSEStream ? value.data : utf8Decoder.decode(value, { stream: true }); const rawData = value.data;
if (isSSEStream && rawData === '[DONE]') return; if (rawData === '[DONE]') return;
tryParseStreamingError(response, rawData); tryParseStreamingError(response, rawData);
const parsed = JSON.parse(rawData); const parsed = JSON.parse(rawData);
@ -1750,7 +1742,7 @@ function getStreamingReply(data) {
if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) { if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
return data?.completion || ''; return data?.completion || '';
} else if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) { } else if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) {
return data?.candidates[0].content.parts[0].text || ''; return data?.candidates?.[0]?.content?.parts?.[0]?.text || '';
} else { } else {
return data.choices[0]?.delta?.content || data.choices[0]?.message?.content || data.choices[0]?.text || ''; return data.choices[0]?.delta?.content || data.choices[0]?.message?.content || data.choices[0]?.text || '';
} }
@ -2173,8 +2165,13 @@ class ChatCompletion {
let squashedMessages = []; let squashedMessages = [];
for (let message of this.messages.collection) { 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 (!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.content += '\n' + message.content;
lastMessage.tokens = tokenHandler.count({ role: lastMessage.role, content: lastMessage.content }); lastMessage.tokens = tokenHandler.count({ role: lastMessage.role, content: lastMessage.content });
} }

View File

@ -38,7 +38,7 @@ import { tags } from './tags.js';
import { tokenizers } from './tokenizers.js'; import { tokenizers } from './tokenizers.js';
import { BIAS_CACHE } from './logit-bias.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 { export {
loadPowerUserSettings, loadPowerUserSettings,
@ -682,6 +682,7 @@ async function CreateZenSliders(elmnt) {
sliderID == 'top_k_textgenerationwebui' || sliderID == 'top_k_textgenerationwebui' ||
sliderID == 'top_k' || sliderID == 'top_k' ||
sliderID == 'rep_pen_slope' || sliderID == 'rep_pen_slope' ||
sliderID == 'smoothing_factor_textgenerationwebui' ||
sliderID == 'min_length_textgenerationwebui') { sliderID == 'min_length_textgenerationwebui') {
offVal = 0; offVal = 0;
} }
@ -696,6 +697,9 @@ async function CreateZenSliders(elmnt) {
sliderID == 'encoder_rep_pen_textgenerationwebui' || sliderID == 'encoder_rep_pen_textgenerationwebui' ||
sliderID == 'temp_textgenerationwebui' || sliderID == 'temp_textgenerationwebui' ||
sliderID == 'temp' || sliderID == 'temp' ||
sliderID == 'min_temp_textgenerationwebui' ||
sliderID == 'max_temp_textgenerationwebui' ||
sliderID == 'dynatemp_exponent_textgenerationwebui' ||
sliderID == 'guidance_scale_textgenerationwebui' || sliderID == 'guidance_scale_textgenerationwebui' ||
sliderID == 'guidance_scale') { sliderID == 'guidance_scale') {
offVal = 1; offVal = 1;
@ -703,6 +707,9 @@ async function CreateZenSliders(elmnt) {
if (sliderID == 'guidance_scale_textgenerationwebui') { if (sliderID == 'guidance_scale_textgenerationwebui') {
numSteps = 78; numSteps = 78;
} }
if (sliderID == 'top_k_textgenerationwebui') {
sliderMin = 0;
}
//customize amt gen steps //customize amt gen steps
if (sliderID !== 'amount_gen' && sliderID !== 'rep_pen_range_textgenerationwebui') { if (sliderID !== 'amount_gen' && sliderID !== 'rep_pen_range_textgenerationwebui') {
stepScale = sliderRange / numSteps; stepScale = sliderRange / numSteps;
@ -1981,10 +1988,51 @@ async function updateTheme() {
toastr.success('Theme saved.'); 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. * Saves the current theme to the server.
* @param {string|undefined} name Theme name. If undefined, a popup will be shown to enter a name. * @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) { async function saveTheme(name = undefined) {
if (typeof name !== 'string') { if (typeof name !== 'string') {
@ -2056,6 +2104,8 @@ async function saveTheme(name = undefined) {
power_user.theme = name; power_user.theme = name;
saveSettingsDebounced(); saveSettingsDebounced();
} }
return theme;
} }
async function saveMovingUI() { async function saveMovingUI() {
@ -3278,6 +3328,30 @@ $(document).ready(() => {
reloadCurrentChat(); 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 () { $(document).on('click', '#debug_table [data-debug-function]', function () {
const functionId = $(this).data('debug-function'); const functionId = $(this).data('debug-function');
const functionRecord = debug_functions.find(f => f.functionId === functionId); const functionRecord = debug_functions.find(f => f.functionId === functionId);

View File

@ -1121,6 +1121,12 @@ function findCharacterIndex(name) {
(a, b) => a.includes(b), (a, b) => a.includes(b),
]; ];
const exactAvatarMatch = characters.findIndex(x => x.avatar === name);
if (exactAvatarMatch !== -1) {
return exactAvatarMatch;
}
for (const matchType of matchTypes) { for (const matchType of matchTypes) {
const index = characters.findIndex(x => matchType(x.name.toLowerCase(), name.toLowerCase())); const index = characters.findIndex(x => matchType(x.name.toLowerCase(), name.toLowerCase()));
if (index !== -1) { if (index !== -1) {

View File

@ -16,6 +16,8 @@
<li><tt>&lcub;&lcub;mesExamplesRaw&rcub;&rcub;</tt> unformatted Dialogue Examples <b>(only for Story String)</b></li> <li><tt>&lcub;&lcub;mesExamplesRaw&rcub;&rcub;</tt> unformatted Dialogue Examples <b>(only for Story String)</b></li>
<li><tt>&lcub;&lcub;user&rcub;&rcub;</tt> your current Persona username</li> <li><tt>&lcub;&lcub;user&rcub;&rcub;</tt> your current Persona username</li>
<li><tt>&lcub;&lcub;char&rcub;&rcub;</tt> the Character's name</li> <li><tt>&lcub;&lcub;char&rcub;&rcub;</tt> the Character's name</li>
<li><tt>&lcub;&lcub;group&rcub;&rcub;</tt> a comma-separated list of group member names or the character name in solo chats. Alias: &lcub;&lcub;charIfNotGroup&rcub;&rcub;</li>
<li><tt>&lcub;&lcub;model&rcub;&rcub;</tt> a text generation model name for the currently selected API. <b>Can be inaccurate!</b></li>
<li><tt>&lcub;&lcub;lastMessage&rcub;&rcub;</tt> - the text of the latest chat message.</li> <li><tt>&lcub;&lcub;lastMessage&rcub;&rcub;</tt> - the text of the latest chat message.</li>
<li><tt>&lcub;&lcub;lastMessageId&rcub;&rcub;</tt> index # of the latest chat message. Useful for slash command batching.</li> <li><tt>&lcub;&lcub;lastMessageId&rcub;&rcub;</tt> index # of the latest chat message. Useful for slash command batching.</li>
<li><tt>&lcub;&lcub;firstIncludedMessageId&rcub;&rcub;</tt> - the ID of the first message included in the context. Requires generation to be ran at least once in the current session.</li> <li><tt>&lcub;&lcub;firstIncludedMessageId&rcub;&rcub;</tt> - the ID of the first message included in the context. Requires generation to be ran at least once in the current session.</li>

0
public/user/.gitkeep Normal file
View File

View File

@ -1,5 +1,6 @@
const DIRECTORIES = { const DIRECTORIES = {
worlds: 'public/worlds/', worlds: 'public/worlds/',
user: 'public/user',
avatars: 'public/User Avatars', avatars: 'public/User Avatars',
images: 'public/img/', images: 'public/img/',
userImages: 'public/user/images/', userImages: 'public/user/images/',

View File

@ -8,7 +8,7 @@ const { DIRECTORIES, UNSAFE_EXTENSIONS } = require('../constants');
const { jsonParser } = require('../express-common'); const { jsonParser } = require('../express-common');
const { clientRelativePath } = require('../util'); 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. * 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' }); const fileStream = fs.createWriteStream(destination, { flags: 'wx' });
await finished(res.body.pipe(fileStream)); 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 // Move into asset place
console.debug('Download finished, moving file from', temp_path, 'to', file_path); console.debug('Download finished, moving file from', temp_path, 'to', file_path);
fs.renameSync(temp_path, file_path); fs.renameSync(temp_path, file_path);

View File

@ -267,7 +267,7 @@ async function sendMakerSuiteRequest(request, response) {
? (stream ? 'streamGenerateContent' : 'generateContent') ? (stream ? 'streamGenerateContent' : 'generateContent')
: (isText ? 'generateText' : 'generateMessage'); : (isText ? 'generateText' : 'generateMessage');
const generateResponse = await fetch(`https://generativelanguage.googleapis.com/${apiVersion}/models/${model}:${responseType}?key=${apiKey}`, { const generateResponse = await fetch(`https://generativelanguage.googleapis.com/${apiVersion}/models/${model}:${responseType}?key=${apiKey}${stream ? '&alt=sse' : ''}`, {
body: JSON.stringify(body), body: JSON.stringify(body),
method: 'POST', method: 'POST',
headers: { headers: {
@ -279,36 +279,8 @@ async function sendMakerSuiteRequest(request, response) {
// have to do this because of their busted ass streaming endpoint // have to do this because of their busted ass streaming endpoint
if (stream) { if (stream) {
try { try {
let partialData = ''; // Pipe remote SSE stream to Express response
generateResponse.body.on('data', (data) => { forwardFetchResponse(generateResponse, response);
const chunk = data.toString();
if (chunk.startsWith(',') || chunk.endsWith(',') || chunk.startsWith('[') || chunk.endsWith(']')) {
partialData = chunk.slice(1);
} else {
partialData += chunk;
}
while (true) {
let json;
try {
json = JSON.parse(partialData);
} catch (e) {
break;
}
response.write(JSON.stringify(json));
partialData = '';
}
});
request.socket.on('close', function () {
if (generateResponse.body instanceof Readable) generateResponse.body.destroy();
response.end();
});
generateResponse.body.on('end', () => {
console.log('Streaming request finished');
response.end();
});
} catch (error) { } catch (error) {
console.log('Error forwarding streaming response:', error); console.log('Error forwarding streaming response:', error);
if (!response.headersSent) { if (!response.headersSent) {
@ -541,8 +513,11 @@ router.post('/status', jsonParser, async function (request, response_getstatus_o
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.OPENROUTER) { } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.OPENROUTER) {
api_url = 'https://openrouter.ai/api/v1'; api_url = 'https://openrouter.ai/api/v1';
api_key_openai = readSecret(SECRET_KEYS.OPENROUTER); api_key_openai = readSecret(SECRET_KEYS.OPENROUTER);
// OpenRouter needs to pass the referer: https://openrouter.ai/docs // OpenRouter needs to pass the Referer and X-Title: https://openrouter.ai/docs#requests
headers = { 'HTTP-Referer': request.headers.referer }; headers = {
'HTTP-Referer': 'https://sillytavern.app',
'X-Title': 'SillyTavern',
};
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.MISTRALAI) { } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.MISTRALAI) {
api_url = new URL(request.body.reverse_proxy || API_MISTRAL).toString(); 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); api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(SECRET_KEYS.MISTRALAI);
@ -728,8 +703,11 @@ router.post('/generate', jsonParser, function (request, response) {
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.OPENROUTER) { } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.OPENROUTER) {
apiUrl = 'https://openrouter.ai/api/v1'; apiUrl = 'https://openrouter.ai/api/v1';
apiKey = readSecret(SECRET_KEYS.OPENROUTER); apiKey = readSecret(SECRET_KEYS.OPENROUTER);
// OpenRouter needs to pass the referer: https://openrouter.ai/docs // OpenRouter needs to pass the Referer and X-Title: https://openrouter.ai/docs#requests
headers = { 'HTTP-Referer': request.headers.referer }; headers = {
'HTTP-Referer': 'https://sillytavern.app',
'X-Title': 'SillyTavern',
};
bodyParams = { 'transforms': ['middle-out'] }; bodyParams = { 'transforms': ['middle-out'] };
if (request.body.min_p !== undefined) { if (request.body.min_p !== undefined) {