mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-02-07 23:58:42 +01:00
Merge branch 'SillyTavern:staging' into staging
This commit is contained in:
commit
1682818f12
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Default",
|
||||
"story_string": "{{#if description}}{{description}}{{/if}}\n{{#if personality}}{{personality}}{{/if}}\n{{#if scenario}}Scenario: {{scenario}}{{/if}}",
|
||||
"story_string": "{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}{{/if}}",
|
||||
"chat_start": "***",
|
||||
"example_separator": "***"
|
||||
}
|
||||
|
6
public/context/Roleplay.json
Normal file
6
public/context/Roleplay.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "Roleplay",
|
||||
"story_string": "### Input:\n{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}{{/if}}",
|
||||
"chat_start": "### New Roleplay:",
|
||||
"example_separator": "### New Roleplay:"
|
||||
}
|
6
public/context/simple-proxy-for-tavern.json
Normal file
6
public/context/simple-proxy-for-tavern.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "simple-proxy-for-tavern",
|
||||
"story_string": "### Input:\n{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}### Response:\n(OOC) Understood. I will take this info into account for the roleplay. (end OOC)",
|
||||
"chat_start": "### New Roleplay:",
|
||||
"example_separator": "### New Roleplay:"
|
||||
}
|
558
public/i18n.json
558
public/i18n.json
@ -2,7 +2,8 @@
|
||||
"lang": [
|
||||
"zh-cn",
|
||||
"ja-jp",
|
||||
"ko-kr"
|
||||
"ko-kr",
|
||||
"ru-ru"
|
||||
],
|
||||
"zh-cn": {
|
||||
"clickslidertips": "点击滑块右侧数字可手动输入",
|
||||
@ -1671,5 +1672,560 @@
|
||||
"Select this as default persona for the new chats.": "새로 열리는 채팅에서 기본 주인공으로 설정",
|
||||
"Change persona image": "주인공 아바타 바꾸기",
|
||||
"Delete persona": "주인공 삭제하기"
|
||||
},
|
||||
"ru-ru": {
|
||||
"clickslidertips": "Можно установить вручную, использовав цифру рядом с ползунком",
|
||||
"kobldpresets": "Предустановки Kobold",
|
||||
"guikoboldaisettings": "Интерфейс KoboldAI",
|
||||
"novelaipreserts": "Предустановки NovelAI",
|
||||
"default": "По умолчанию",
|
||||
"openaipresets": "Предустановки OpenAI",
|
||||
"text gen webio(ooba) presets": "Предустановки WebUI(ooba)",
|
||||
"response legth(tokens)": "Длина ответа (в токенах)",
|
||||
"select": "Выбрать",
|
||||
"context size(tokens)": "Размер контекста (в токенах)",
|
||||
"unlocked": "Неограниченный",
|
||||
"only select modls support context sizes greater than 2048 tokens. proceed only is you know you're doing": "Только отдельные модели поддерживают контекст, превышающий 2048 токенов. Используйте только если понимаете, что делаете.",
|
||||
"rep.pen": "Rep. Pen.",
|
||||
"rep.pen range": "Диапазон Rep. Pen.",
|
||||
"temperature": "Температура",
|
||||
"Encoder Rep. Pen.": "Расшифровщик Rep. Pen.",
|
||||
"No Repeat Ngram Size": "No Repeat Ngram Size",
|
||||
"Min Length": "Минимальная длина",
|
||||
"OpenAI Reverse Proxy": "Прокси с OpenAI",
|
||||
"Alternative server URL (leave empty to use the default value).": "Альтернативный URL сервера (оставьте пустым для стандартного значения)",
|
||||
"Remove your real OAI API Key from the API panel BEFORE typing anything into this box": "Удалите свой личный OAI API Key из панели API прежде, чем вносить сюда ЧТО УГОДНО",
|
||||
"We cannot provide support for problems encountered while using an unofficial OpenAI proxy": "Мы не сможем предоставить помощь с проблемами, с которыми вы столкнетесь при использовании неофициальных прокси для OpenAI",
|
||||
"Legacy Streaming Processing": "Старый способ потокового вывода текста",
|
||||
"Enable this if the streaming doesn't work with your proxy": "Включите это, если потоковый вывод текста не работает с вашим прокси",
|
||||
"Context Size (tokens)": "Размер контекста (в токенах)",
|
||||
"Max Response Length (tokens)": "Максимальная длина ответа (в токенах)",
|
||||
"Temperature": "Temperature",
|
||||
"Frequency Penalty": "Frequency Penalty",
|
||||
"Presence Penalty": "Presence Penalty",
|
||||
"Top-p": "Top-p",
|
||||
"Display bot response text chunks as they are generated": "Отображать ответ ИИ по мере генерации текста",
|
||||
"Top A": "Top-a",
|
||||
"Typical Sampling": "Typical Sampling",
|
||||
"Tail Free Sampling": "Tail Free Sampling",
|
||||
"Rep. Pen. Slope": "Rep. Pen. Slope",
|
||||
"Single-line mode": "Режим одной строки",
|
||||
"Top K": "Top-k",
|
||||
"Top P": "Top-p",
|
||||
"Do Sample": "Do Sample",
|
||||
"Add BOS Token": "Добавить BOS-токен",
|
||||
"Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative.": "Добавлять BOS-токен в начале инструкции. Выключение этого может сделать ответы более креативными. ",
|
||||
"Ban EOS Token": "Заблокировать EOS-токен",
|
||||
"Ban the eos_token. This forces the model to never end the generation prematurely": "Блокировка EOS-токена вынудит модель никогда не завершать генерацию преждевременно",
|
||||
"Skip Special Tokens": "Пропускать специальные токены",
|
||||
"Beam search": "Поиск Beam",
|
||||
"Number of Beams": "Количество Beam",
|
||||
"Length Penalty": "Length Penalty",
|
||||
"Early Stopping": "Преждевременная остановка",
|
||||
"Contrastive search": "Contrastive search",
|
||||
"Penalty Alpha": "Penalty Alpha",
|
||||
"Seed": "Зерно",
|
||||
"Inserts jailbreak as a last system message.": "Вставлять JailBreak последним системным сообщением.",
|
||||
"This tells the AI to ignore its usual content restrictions.": "Сообщает AI о необходимости игнорировать стандартные ограничения контента.",
|
||||
"NSFW Encouraged": "Поощрять NSFW",
|
||||
"Tell the AI that NSFW is allowed.": "Сообщает AI, что ему позволено генерировать NSFW.",
|
||||
"NSFW Prioritized": "Предпочитать NSFW",
|
||||
"NSFW prompt text goes first in the prompt to emphasize its effect.": "Отправлять NSFW-инструкцию в начале для усиления его эффекта",
|
||||
"Streaming": "Потоковый вывод текста",
|
||||
"Display the response bit by bit as it is generated.": "Отображать ответ по кускам в процессе генерации.",
|
||||
"When this is off, responses will be displayed all at once when they are complete.": "Если данная функция отключена, ответ будет отображен полностью после генерации.",
|
||||
"Enhance Definitions": "Улучшенная узнаваемость",
|
||||
"Use OAI knowledge base to enhance definitions for public figures and known fictional characters": "Позволяет использовать базу знаний, улучшающую узнаваемость ИИ публичных лиц и вымышленных персонажей",
|
||||
"Wrap in Quotes": "Заключать в кавычки",
|
||||
"Wrap entire user message in quotes before sending.": "Заключить всё сообщение пользователя в кавычки перед отправкой.",
|
||||
"Leave off if you use quotes manually for speech.": "Оставьте выключенным, если вручную выставляете кавычки для прямой речи.",
|
||||
"Main prompt": "Основная инструкция",
|
||||
"The main prompt used to set the model behavior": "Основная инструкция, используемая для установки поведения модели",
|
||||
"NSFW prompt": "NSFW-инструкция",
|
||||
"Prompt that is used when the NSFW toggle is on": "Инструкция, отправляемая ИИ при включенном поощрении NSFW.",
|
||||
"Jailbreak prompt": "Инструкция для JailBreak",
|
||||
"Prompt that is used when the Jailbreak toggle is on": "Инструкция, отправляемая ИИ при включенном JailBreak.",
|
||||
"Impersonation prompt": "Инструкция для перевоплощения",
|
||||
"Prompt that is used for Impersonation function": "Инструкция, отправляемая ИИ для генерации действий за пользователя",
|
||||
"Logit Bias": "Logit Bias",
|
||||
"Helps to ban or reenforce the usage of certain words": "Позволяет запретить или поощрять использование определенных слов",
|
||||
"View / Edit bias preset": "Посмотреть/Настроить предустановку для bias",
|
||||
"Add bias entry": "Добавить инструкцию в Bias",
|
||||
"Jailbreak activation message": "Сообщение об активации JailBreak",
|
||||
"Message to send when auto-jailbreak is on.": "Сообщение, отправляемое когда автоматический JailBreak включен.",
|
||||
"Jailbreak confirmation reply": "Подтверждение JailBreak",
|
||||
"Bot must send this back to confirm jailbreak": "Это сообщение будет отправлено ИИ при успешном включении JailBreak.",
|
||||
"Character Note": "Заметки о персонаже",
|
||||
"Influences bot behavior in its responses": "Влияет на поведение ИИ и его ответы.",
|
||||
"API": "API",
|
||||
"KoboldAI": "KoboldAI",
|
||||
"Use Horde": "Использовать Horde",
|
||||
"API url": "API URL",
|
||||
"Register a Horde account for faster queue times": "Заведите учетную запись Horde для ускорения генерации",
|
||||
"Learn how to contribute your idle GPU cycles to the Hord": "Узнайте подробнее о том, как использовать время простоя GPU для Hord",
|
||||
"Adjust context size to worker capabilities": "Уточнить размер контекста для возможностей рабочих",
|
||||
"Adjust response length to worker capabilities": "Уточнить длинну ответа для возможностей рабочий",
|
||||
"API key": "API-ключ",
|
||||
"Register": "Регист",
|
||||
"For privacy reasons": "В целях конфиденциальности API-ключ будет скрыт после перезагрузки страницы",
|
||||
"Model": "Модель",
|
||||
"Hold Control / Command key to select multiple models.": "Удерживайте Control / Command для выбора нескольких моделей.",
|
||||
"Horde models not loaded": "Модели Horde не загружены",
|
||||
"Not connected": "Нет подключения",
|
||||
"Novel API key": "API-ключ для NovelAI",
|
||||
"Follow": "Следуйте",
|
||||
"these directions": "данным инструкциям",
|
||||
"to get your NovelAI API key.": "чтобы получить свой API-ключ от NovelAI",
|
||||
"Enter it in the box below": "Введите это в окошко ниже",
|
||||
"Novel AI Model": "Модель NovelAI",
|
||||
"Euterpe": "Euterpe",
|
||||
"Krake": "Krake",
|
||||
"No connection": "Нет подключения",
|
||||
"oobabooga/text-generation-webui": "",
|
||||
"Make sure you run it with": "Убедитесь, что при запуске указали аргумент --api",
|
||||
"Blocking API url": "Блокирующий API URL",
|
||||
"Streaming API url": "Потоковый API URL",
|
||||
"to get your OpenAI API key.": "для получения API-ключа OpenAI",
|
||||
"OpenAI Model": "Модель OpenAI",
|
||||
"View API Usage Metrics": "Посмотреть статистику использования API",
|
||||
"Bot": "Бот:",
|
||||
"Connect to the API": "Соединение с API",
|
||||
"Auto-connect to Last Server": "Автоматическое подключение к последнему серверу",
|
||||
"View hidden API keys": "Посмотреть скрытые API-ключи",
|
||||
"Advanced Formatting": "Расширенное форматирование",
|
||||
"AutoFormat Overrides": "Замена АвтоФормата",
|
||||
"Disable description formatting": "Отключить форматирование описания",
|
||||
"Disable personality formatting": "Отключить форматирование личности",
|
||||
"Disable scenario formatting": "Отключить форматирование сценария",
|
||||
"Disable example chats formatting": "Отключить форматирование примеров чата",
|
||||
"Disable chat start formatting": "Отключить форматирование начала чата",
|
||||
"Custom Chat Separator": "Пользовательское разделение чата",
|
||||
"Instruct mode": "Режим Instruct",
|
||||
"Enabled": "Включен",
|
||||
"Wrap Sequences with Newline": "Отделять последовательности красной строкой",
|
||||
"Include Names": "Показывать имена",
|
||||
"System Prompt": "Системная инструкция",
|
||||
"Input Sequence": "Input Sequence",
|
||||
"Output Sequence": "Output Sequence",
|
||||
"System Sequence": "System Sequence",
|
||||
"Stop Sequence": "Stop Sequence",
|
||||
"Context Formatting": "Форматирование контекста",
|
||||
"Tokenizer": "Токенайзер",
|
||||
"None / Estimated": "Отсутствует/Приблизительно",
|
||||
"Sentencepiece (LLaMA)": "Sentencepiece(LLaMA)",
|
||||
"Token Padding": "Заполнение токенов",
|
||||
"Always add character's name to prompt": "Всегда добавлять имя персонажа в инструкции",
|
||||
"Keep Example Messages in Prompt": "Сохранять примеры сообщений в инструкции",
|
||||
"Remove Empty New Lines from Output": "Удалять пустые строчки из вывода",
|
||||
"Pygmalion Formatting": "Форматирование Pygmalion",
|
||||
"Disabled for all models": "Выключено для всех моделей",
|
||||
"Automatic (based on model name)": "Автоматически (выбор по названию модели)",
|
||||
"Enabled for all models": "Включить для всех моделей",
|
||||
"Multigen": "Мултиген",
|
||||
"First chunk (tokens)": "Первый отрезок (в токенах)",
|
||||
"Next chunks (tokens)": "Следующий отрезок (в токенах)",
|
||||
"Anchors Order": "Порядок Anchors",
|
||||
"Character then Style": "Персонаж после Стиля",
|
||||
"Style then Character": "Стиль после Персонажа",
|
||||
"Character Anchor": "Anchors Персонажа",
|
||||
"Style Anchor": "Стиль Anchors",
|
||||
"World Info": "Информация о мире",
|
||||
"Scan Depth": "Глубина сканирования",
|
||||
"depth": "глубина",
|
||||
"Token Budget": "Объем токенов",
|
||||
"budget": "объем",
|
||||
"Recursive scanning": "Рекурсивное сканирование",
|
||||
"Soft Prompt": "Мягкая инструкция",
|
||||
"About soft prompts": "О мягких инструкциях",
|
||||
"None": "Отсутствует",
|
||||
"User Settings": "Настройки пользователя",
|
||||
"UI Customization": "Настройки UI",
|
||||
"Avatar Style": "Стиль аватаров",
|
||||
"Circle": "Круглые",
|
||||
"Rectangle": "Прямоугольные",
|
||||
"Chat Style": "Стиль чата",
|
||||
"Default": "По умолчанию",
|
||||
"Bubbles": "Пузыри",
|
||||
"Chat Width (PC)": "Ширина чата (на PC):",
|
||||
"No Blur Effect": "Отключить эффект размытия",
|
||||
"No Text Shadows": "Отключить тень текста",
|
||||
"Waifu Mode": "!!!РЕЖИМ ВАЙФУ!!!",
|
||||
"Message Timer": "Таймер сообщений",
|
||||
"Characters Hotswap": "Смена персонажей на лету",
|
||||
"Movable UI Panels": "Перемещение панелей интерфейса",
|
||||
"Reset Panels": "Сбросить панели",
|
||||
"UI Colors": "Цвета интерфейса",
|
||||
"Main Text": "Основной текст",
|
||||
"Italics Text": "Курсивный текст",
|
||||
"Quote Text": "Текст в кавычках",
|
||||
"Shadow Color": "Цвет теней",
|
||||
"FastUI BG": "Фон FastUI",
|
||||
"Blur Tint": "Оттенок размытия",
|
||||
"Font Scale": "Размер текста",
|
||||
"Blur Strength": "Сила размытия",
|
||||
"Text Shadow Width": "Размер теней текста",
|
||||
"UI Theme Preset": "Предустановки интерфейса",
|
||||
"Power User Options": "Продвинутые параметры",
|
||||
"Swipes": "Свайвы",
|
||||
"Background Sound Only": "Только фоновый звук",
|
||||
"Auto-load Last Chat": "Автоматически загружать последий чат",
|
||||
"Auto-save Message Edits": "Автоматически сохранять отредактированные сообщения",
|
||||
"Auto-fix Markdown": "Автоматическое исправление подчеркиваний",
|
||||
"Allow : in bot messages": "Разрешить : в сообщениях ИИ",
|
||||
"Auto-scroll Chat": "Автоматическая прокрутка чата",
|
||||
"Render Formulas": "Рендер формул",
|
||||
"Send on Enter": "Отправка на Enter",
|
||||
"Always disabled": "Всегда выключена",
|
||||
"Automatic (desktop)": "Автоматически (системные настройки)",
|
||||
"Always enabled": "Всегда включена",
|
||||
"Name": "Имя",
|
||||
"Your Avatar": "Ваш Аватар",
|
||||
"Extensions API:": "API для расширений",
|
||||
"SillyTavern-extras": "SillyTavern-extras",
|
||||
"Auto-connect": "Автоматическое соединение",
|
||||
"Active extensions": "Актививные расширения",
|
||||
"Extension settings": "Настройки расширений",
|
||||
"Description": "Описание",
|
||||
"First message": "Первое сообщение",
|
||||
"Group Controls": "Контроль группы",
|
||||
"Group reply strategy": "Сортировка ответов в группе",
|
||||
"Natural order": "Обычный порядок",
|
||||
"List order": "Порядок по листу",
|
||||
"Allow self responses": "Разрешить ответ себе",
|
||||
"Auto Mode": "Автоматический режим",
|
||||
"Add Members": "Добавить членов",
|
||||
"Current Members": "Текущие члены",
|
||||
"text": "текст",
|
||||
"Delete": "Удалить",
|
||||
"Cancel": "Отменить",
|
||||
"Advanced Defininitions": "Улучшенная узнаваемость",
|
||||
"Personality summary": "Личная сводка",
|
||||
"A brief description of the personality": "Краткое описание личности",
|
||||
"Scenario": "Сценарий",
|
||||
"Circumstances and context of the dialogue": "Обстоятельства и контекст диалога",
|
||||
"Talkativeness": "Разговорчивость",
|
||||
"How often the chracter speaks in": "Как часто персонаж говорит",
|
||||
"group chats!": "в груповых чатах",
|
||||
"Shy": "Застенчивый",
|
||||
"Normal": "Обычный",
|
||||
"Chatty": "Разговорчивый",
|
||||
"Examples of dialogue": "Примеры диалога",
|
||||
"Forms a personality more clearly": "Определите личность более точно",
|
||||
"Save": "Сохранить",
|
||||
"World Info Editor": "Редактирование информации о мире",
|
||||
"New summary": "Новая переменная",
|
||||
"Export": "Экспорт",
|
||||
"Delete World": "Удалить мир",
|
||||
"Chat History": "История чата",
|
||||
"Group Chat Scenario Override": "Замещение сценария группового чата",
|
||||
"All group members will use the following scenario text instead of what is specified in their character cards.": "Все участники группы будут использовать следующий сценарий, вместо обозначенного в карточках персонажей",
|
||||
"Keywords": "Ключевые слова",
|
||||
"Separate with commas": "Разделять запятыми",
|
||||
"Secondary Required Keywords": "Вторичные ключевые слова",
|
||||
"Content": "Содержание",
|
||||
"What this keyword should mean to the AI": "Значение данного ключевого слова для ИИ",
|
||||
"Memo/Note": "Напоминание",
|
||||
"Not sent to AI": "Не отправлять ИИ",
|
||||
"Constant": "Постоянно",
|
||||
"Selective": "Выборочно",
|
||||
"Before Char": "Перед Персонажем",
|
||||
"After Char": "После Персонажа",
|
||||
"Insertion Order": "Порядок внесения",
|
||||
"Tokens:": "Токены",
|
||||
"Disable": "Отключено",
|
||||
"${characterName}": "${имяПерсонажа}",
|
||||
"CHAR": "ПЕРСОНАЖ",
|
||||
"is typing": "Печатает...",
|
||||
"Back to parent chat": "Вернуться в основной чат",
|
||||
"Save bookmark": "Сохранить закладку",
|
||||
"Convert to group": "Превратить в группу",
|
||||
"Start new chat": "Начать новый чат",
|
||||
"View past chats": "Посмотреть прошлые чаты",
|
||||
"Delete messages": "Удалить сообщения",
|
||||
"Impersonate": "Перевоплощение",
|
||||
"Regenerate": "Повторная генерация",
|
||||
"PNG": "PNG",
|
||||
"JSON": "JSON",
|
||||
"WEBP": "WEBP",
|
||||
"presets": "Предустановки",
|
||||
"Message Sound": "Звук сообщения",
|
||||
"Author's Note": "Авторские заметки",
|
||||
"Send Jailbreak": "Отправлять JailBreak",
|
||||
"Replace empty message": "Заменять пустые сообщения",
|
||||
"Send this text instead of nothing when the text box is empty.": "Этот текст будет отправлен в случае отсутствия текста на отправку.",
|
||||
"NSFW avoidance prompt": "Инструкции для избегания NSFW",
|
||||
"Prompt that is used when the NSFW toggle is off": "Инструкции, используемые, когда поощрение NSFW отключено",
|
||||
"Advanced prompt bits": "Дополнительные инструкции",
|
||||
"World Info format": "Шаблон форматирования Информации о мире",
|
||||
"Wraps activated World Info entries before inserting into the prompt. Use {0} to mark a place where the content is inserted.": "Выделяет активную Информацию о мире перед внесением в инструкции. Используйте {0} чтобы уточнить место с необходимой информацией.",
|
||||
"Unrestricted maximum value for the context slider": "Неограниченное максимальное значение для ползунка с размером контекста",
|
||||
"Chat Completion Source": "Источник для Chat Completion",
|
||||
"Avoid sending sensitive information to the Horde.": "Избегайте отправки личной информации Horde",
|
||||
"Review the Privacy statement": "Посмотреть Privacy statement",
|
||||
"Learn how to contribute your idel GPU cycles to the Horde": "Изучите, как использовать GPU в состоянии простоя на благо Horde",
|
||||
"Trusted workers only": "Только доверенные рабочие",
|
||||
"For privacy reasons, your API key will be hidden after you reload the page.": "По причинам безопасности ваш API-ключ будет скрыт после перезагрузки страницы.",
|
||||
"-- Horde models not loaded --": "--Модель Horde не загружена--",
|
||||
"Example: http://127.0.0.1:5000/api ": "Пример: http://127.0.0.1:5000/api",
|
||||
"No connection...": "Нет соединения...",
|
||||
"Get your NovelAI API Key": "Получите свой API-ключ для NovelAI",
|
||||
"KoboldAI Horde": "KoboldAI Horde",
|
||||
"Text Gen WebUI (ooba)": "Text Gen WebUI (ooba)",
|
||||
"NovelAI": "NovelAI",
|
||||
"Chat Completion (OpenAI, Claude, Window/OpenRouter, Scale)": "Дополнение диалога (OpenAI, Claude, Window/OpenRouter, Scale)",
|
||||
"OpenAI API key": "API-ключ для OpenAI",
|
||||
"Trim spaces": "Обрезать пробелы",
|
||||
"Trim Incomplete Sentences": "Обрезать неоконченные предложения",
|
||||
"Include Newline": "Использовать красную строку",
|
||||
"Non-markdown strings": "Неподчеркиваемые Strings",
|
||||
"Replace Macro in Sequences": "Заменить макросы в последовательности",
|
||||
"Presets": "Предустановки",
|
||||
"Separator": "Разделитель",
|
||||
"Start Reply With": "Начинать ответ с",
|
||||
"Show reply prefix in chat": "Показывать префиксы ответов в чате",
|
||||
"Worlds/Lorebooks": "Миры/Сведения",
|
||||
"Active World(s)": "Активные миры",
|
||||
"Character Lore Insertion Strategy": "Порядок включения сведений",
|
||||
"Sorted Evenly": "Равномерная сортировка",
|
||||
"Character Lore First": "Сначала сведения о персонаже",
|
||||
"Global Lore First": "Сначала общие сведения",
|
||||
"-- World Info not found --": "Информация о Мире не найдена",
|
||||
"Recursive Scan": "Рекурсивное сканирование",
|
||||
"Case Sensitive": "Учитывать регистр",
|
||||
"Match whole words": "Только полное совпадение",
|
||||
"World/Lore Editor": "Редактировать Мир/Сведения",
|
||||
"--- None ---": "---Отсутствует---",
|
||||
"Comma seperated (ignored if empty)": "Разделение запятыми (не используется, если оставлено пустым)",
|
||||
"Use Probability": "Использовать вероятность",
|
||||
"Exclude from recursion": "Исключить из рекурсии",
|
||||
"Position:": "Положение:",
|
||||
"Before Char Defs": "Перед определением Персонажа",
|
||||
"After Char Defs": "После определения Персонажа",
|
||||
"Before AN": "Перед AN",
|
||||
"After AN": "После AN",
|
||||
"Order:": "Порядок:",
|
||||
"Probability:": "Вероятность:",
|
||||
"Delete Entry": "Удалить запись:",
|
||||
"User Message Blur Tint": "Оттенок размытия сообщения пользователя",
|
||||
"AI Message Blur Tint": "Оттенок размытия сообщения ИИ",
|
||||
"Chat Style:": "Стиль чата",
|
||||
"Chat Width (PC):": "Ширина чата (для ПК)",
|
||||
"Chat Timestamps": "Временные обозначения в чате",
|
||||
"Message IDs": "ID сообщений",
|
||||
"Prefer Character Card Prompt": "Предпочитать инструкции из Карточки Персонажа",
|
||||
"Prefer Character Card Jailbreak": "Предпочитать JailBreak из Карточки Персонажа",
|
||||
"Press Send to continue": "Нажатие Отправить для продолжения",
|
||||
"Log prompts to console": "Выводы журнала в консоли",
|
||||
"Never resize avatars": "Никогда не менять размер аватаров",
|
||||
"Show avatar filenames": "Показывать названия файлов аватаров",
|
||||
"Import Card Tags": "Импорт меток Карточки",
|
||||
"Confirm message deletion": "Подтверждение удаления сообщений",
|
||||
"Spoiler Free Mode": "Режим без спойлеров",
|
||||
"Auto-swipe": "Автоматические свайпы",
|
||||
"Minimum generated message length": "Минимальная длина сгенерированных сообщений",
|
||||
"Blacklisted words": "Запрещенные слова",
|
||||
"Blacklisted word count to swipe": "Количество запрещенных слов для свайпа",
|
||||
"Reload Chat": "Перезагрузить чат",
|
||||
"Not Connected": "Не подключено",
|
||||
"Persona Management": "Управление Персоной",
|
||||
"Persona Description": "Описание Персоны",
|
||||
"Before Character Card": "Перед Карточкой Персонажа",
|
||||
"After Character Card": "После Карточки Персонажа",
|
||||
"Top of Author's Note": "Перед Авторскими Заметками",
|
||||
"Bottom of Author's Note": "После Авторских Заметок",
|
||||
"How do I use this?": "Как мне это использовать?",
|
||||
"More...": "Узнать больше...",
|
||||
"Link to World Info": "Ссылка на информацию о мире",
|
||||
"Import Card Lore": "Импортировать Карточку Сведений",
|
||||
"Scenario Override": "Замещение сценария",
|
||||
"Rename": "Переименовать",
|
||||
"Character Description": "Описание персонажа",
|
||||
"Creator's Notes": "Заметки создателя",
|
||||
"A-Z": "A-Z",
|
||||
"Z-A": "Z-A",
|
||||
"Newest": "Новейшие",
|
||||
"Oldest": "Старейшие",
|
||||
"Favorites": "Любимые",
|
||||
"Recent": "Последние",
|
||||
"Most chats": "Больше всего чатов",
|
||||
"Least chats": "Меньше всего чатов",
|
||||
"Back": "Назад",
|
||||
"Prompt Overrides (For OpenAI/Claude/Scale APIs, Window/OpenRouter, and Instruct mode)": "Замещение инструкций (Для OpenAI/Claude/Scale API, Window/OpenRouter, и Режима Instruct)",
|
||||
"Insert {{original}} into either box to include the respective default prompt from system settings.": "Внесите {{original}} в любое поле для внесения стандартных инструкций из системных настроек",
|
||||
"Main Prompt": "Главные инструкции",
|
||||
"Jailbreak": "JailBreak",
|
||||
"Creator's Metadata (Not sent with the AI prompt)": "Сведения о создателе (не отправляются ИИ с инструкциями)",
|
||||
"Everything here is optional": "Всё в данных полях опционально",
|
||||
"Created by": "Создано",
|
||||
"Character Version": "Версия Персонажа",
|
||||
"Tags to Embed": "Теги для встраивания",
|
||||
"How often the character speaks in group chats!": "Как часто персонаж говорит в групповых чатах",
|
||||
"Important to set the character's writing style.": "Важные замечания для стиля написания персонажа",
|
||||
"ATTENTION!": "ВНИМАНИЕ!",
|
||||
"Samplers Order": "Порядок семплирования",
|
||||
"Samplers will be applied in a top-down order. Use with caution.": "Семплирование будет применено в порядке сверху-вниз. Используйте с осторожностью.",
|
||||
"Repetition Penalty": "Наказание за повторы",
|
||||
"Epsilon Cutoff": "Epsilon Cutoff",
|
||||
"Eta Cutoff": "Eta Cutoff",
|
||||
"Rep. Pen. Range.": "Размер наказания за повторы",
|
||||
"Rep. Pen. Freq.": "Частота наказания за повторы",
|
||||
"Rep. Pen. Presence": "Наличие наказания за повторы",
|
||||
"Enter it in the box below:": "Введите в поле ниже:",
|
||||
"separate with commas w/o space between": "разделять запятыми без пробелов между:",
|
||||
"Document": "Документ",
|
||||
"Suggest replies": "Предлагать ответы",
|
||||
"Show suggested replies. Not all bots support this.": "Показывать предлагаемые ответы. Не все боты поддерживают это.",
|
||||
"Use 'Unlocked Context' to enable chunked generation.": "Использовать 'Безлимитный контекст' для активации кусочной генерации",
|
||||
"It extends the context window in exchange for reply generation speed.": "Увеличивает размер контекста в обмен на скорость генерации.",
|
||||
"Continue": "Пролдолжить",
|
||||
"Editing:": "Изменения",
|
||||
"AI reply prefix": "Префикс Ответ ИИ",
|
||||
"Custom Stopping Strings": "Настройка ограничивающий нитей",
|
||||
"JSON serialized array of strings": "JSON ориентированный набор нитей",
|
||||
"words you dont want generated separated by comma ','": "слова которые вы не хотите при генерации здесь, разделенные запятой",
|
||||
"Extensions URL": "URL расширений ",
|
||||
"API Key": "Ключ API",
|
||||
"Enter your name": "Введите свое имя",
|
||||
"Name this character": "Назовите этого персонажа",
|
||||
"Search / Create Tags": "Искать / Создать тэги",
|
||||
"Describe your character's physical and mental traits here.": "Опишите ментальные и физические черты персонажа здесь",
|
||||
"This will be the first message from the character that starts every chat.": "Это булет первое сообщение от Персонажа при начале нового чата",
|
||||
"Chat Name (Optional)": "Название Чата (Необязательно)",
|
||||
"Filter...": "Отфильтровать...",
|
||||
"Search...": "Поиск...",
|
||||
"Any contents here will replace the default Main Prompt used for this character. (v2 spec: system_prompt)": "Все содержание этой ячейки будет заменять стандартный Промт",
|
||||
"Any contents here will replace the default Jailbreak Prompt used for this character. (v2 spec: post_history_instructions)": "Все содержание этой ячейки будет заменять стандартный Джейлбрейк",
|
||||
"(Botmaker's name / Contact Info)": "Ваше имя / Контакты",
|
||||
"(If you want to track character versions)": "Если вы хотите отслеживать модель персонажа",
|
||||
"(Describe the bot, give use tips, or list the chat models it has been tested on. This will be displayed in the character list.)": "Опишите Персонажа, дайте советы, или упомяните на каких моделях он был протестирован",
|
||||
"(Write a comma-separated list of tags)": "Запишите лист тэгов, разделяя запятой",
|
||||
"(A brief description of the personality)": "Краткое описание личности)",
|
||||
"(Circumstances and context of the interaction)": "(Обстоятельства и контекст этого взаимодействия)",
|
||||
"(Examples of chat dialog. Begin each example with START on a new line.)": "(Примеры диалога. Начинайте каждый пример с START или новой линией.)",
|
||||
"Injection text (supports parameters)": "Текст включения (Поддерживает параметры)",
|
||||
"Injection depth": "Глубина включения",
|
||||
"Type here...": "Пишите здесь...",
|
||||
"Comma separated (required)": "Разделено запятыми (Обязательно)",
|
||||
"Comma separated (ignored if empty)": "Разделено запятыми (Игнорируется если пусто)",
|
||||
"What this keyword should mean to the AI, sent verbatim": "Значение этого ключевого слова, отправляется ИИ дословно",
|
||||
"Not sent to the AI": "Не отправляется ИИ",
|
||||
"(This will be the first message from the character that starts every chat)": "(Это будет первое сообщение от персонажа, когда вы начинаете новый чат)",
|
||||
"Not connected to API!": "Нет подключения к API",
|
||||
"AI Response Configuration": "Еастройка Ответа ИИ",
|
||||
"AI Configuration panel will stay open": "Панель Настройки ИИ останется открытой",
|
||||
"Update current preset": "Обновить текущую настройку",
|
||||
"Create new preset": "Создать новую настройку",
|
||||
"Import preset": "Внести настройку",
|
||||
"Export preset": "Скачать настройку",
|
||||
"Delete the preset": "Удалить настройку",
|
||||
"NSFW block goes first in the resulting prompt": "НСФВ блокировка идет первой при отправки Промта",
|
||||
"Enables OpenAI completion streaming": "Включить процесс генерации OpenAI",
|
||||
"Wrap user messages in quotes before sending": "Заключить ответ Пользователя в кавычки",
|
||||
"Restore default prompt": "Восстановить станндартный промт",
|
||||
"New preset": "Новая настройка",
|
||||
"Delete preset": "Удалить настройку",
|
||||
"Restore default jailbreak": "Восстановить стандартный Джейлбрейк",
|
||||
"Restore default reply": "Восстановить стандартный ответ",
|
||||
"Restore defaul note": "Восстановить стандартную заметку",
|
||||
"API Connections": "Соединения API",
|
||||
"Can help with bad responses by queueing only the approved workers. May slowdown the response time.": "Может помочь с плохими ответами ставя в очередь только подтвержденных работников. Может замедлить время ответа.",
|
||||
"Clear your API key": "Очистить свои ключи API",
|
||||
"Refresh models": "Обновить модели",
|
||||
"Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Получите свой OpenRouter API токен используя OAuth. У вас будет открыта вкладка openrouter.ai",
|
||||
"Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!": "Подверждает ваше соединение к API. Знайте, что за это снимут деньги с вашего счета.",
|
||||
"Create New": "Создать новое",
|
||||
"Edit": "Изменить",
|
||||
"World Info & Soft Prompts": "Информация о Мире & Мягкий Промт",
|
||||
"Locked = World Editor will stay open": "Закреплено = Редактирование Мира останется открытым",
|
||||
"Entries can activate other entries by mentioning their keywords": "Записи могут активировать другие записи если в них содержаться ключевые слова",
|
||||
"Lookup for the entry keys in the context will respect the case": "Большая буква имеет значение при активации ключевого слова",
|
||||
"If the entry key consists of only one word, it would not be matched as part of other words": "Если ключевое слово состоит только из одного слова, оно не будет активироваться как часть других слов",
|
||||
"Open all Entries": "Открыть все Записи",
|
||||
"Close all Entries": "Закрыть все Записи",
|
||||
"Create": "Создать",
|
||||
"Import World Info": "Внести Информацию Мира",
|
||||
"Export World Info": "Скачать Информацию Мира",
|
||||
"Delete World Info": "Удалить Информацию Мира",
|
||||
"Rename World Info": "Переименовать Информацию Мира",
|
||||
"Save changes to a new theme file": "Сохранить изменения в новой теме",
|
||||
"removes blur and uses alternative background color for divs": "убирает размытие и использует альтернативный фон для разделов",
|
||||
"If checked and the character card contains a prompt override (System Prompt), use that instead.": "Если выбрано и карточка персонажа содержит собственный промт (Системный Промт), выберите это",
|
||||
"If checked and the character card contains a jailbreak override (Post History Instruction), use that instead.": "Если выбрано и карточка персонажа содержит собственный Джейлбрейк (После Истории Инструкций), выберите это",
|
||||
"AI Response Formatting": "Формат ответа ИИ",
|
||||
"Change Background Image": "Изменить фон",
|
||||
"Extensions": "Расширения",
|
||||
"Click to set a new User Name": "Нажмите что бы выбрать новое имя Личности",
|
||||
"Click to lock your selected persona to the current chat. Click again to remove the lock.": "Нажмите что бы закрепить выьранную личность к текущему чату",
|
||||
"Click to set user name for all messages": "Нажмите что бы закрепить Личность для всех сообщений",
|
||||
"Create a dummy persona": "Создать болванку",
|
||||
"Character Management": "Управление Персонажами",
|
||||
"Locked = Character Management panel will stay open": "Закреплено = Панель Управление Персонажами останется открытой ",
|
||||
"Select/Create Characters": "Выбрать/Создать персонажа",
|
||||
"Token counts may be inaccurate and provided just for reference.": "Счетчик токенов может быть неточным и используется только для примера",
|
||||
"Click to select a new avatar for this character": "Нажмите что бы выбрать новый аватар для этого персонажа",
|
||||
"Add to Favorites": "Добавить в Любимые",
|
||||
"Advanced Definition": "Расширенные Определения",
|
||||
"Character Lore": "Сведения Персонажа",
|
||||
"Export and Download": "Экспортировать и Скачать",
|
||||
"Duplicate Character": "Дублировать персонажа",
|
||||
"Create Character": "Создать персонажа",
|
||||
"Delete Character": "Удалить персонажа",
|
||||
"View all tags": "Показать все тэги",
|
||||
"Click to set additional greeting messages": "Нажмите что бы создать дополнительное вступительное сообщение",
|
||||
"Show / Hide Description and First Message": "Показать/Убрать Описание и Первое сообщение",
|
||||
"Click to select a new avatar for this group": "Нажмите что бы выбрать новый аватар для этого группового чата",
|
||||
"Set a group chat scenario": "Создать сценарий для группового чата",
|
||||
"Restore collage avatar": "Восстановить коллаж персонажа",
|
||||
"Create New Character": "Создать нового персонажа",
|
||||
"Import Character from File": "Внести персонажа из файла",
|
||||
"Import content from external URL": "Вставить содержимое из внешнего URL",
|
||||
"Create New Chat Group": "Создать новый групповой чат",
|
||||
"Characters sorting order": "Упорядовачивание порядка персонажа",
|
||||
"Add chat injection": "Добавить включение в чат",
|
||||
"Remove injection": "Убрать включение",
|
||||
"Remove": "Убрать",
|
||||
"Select a World Info file for": "Выбрать файл для Информации Мира",
|
||||
"Primary Lorebook": "Основной Лорбук",
|
||||
"A selected World Info will be bound to this character as its own Lorebook.": "Информация Мира будет закреплена на персонажем как собственный Лорбук",
|
||||
"When generating an AI reply, it will be combined with the entries from a global World Info selector.": "Когда ИИ генерирует ответ, в него будет включены заметки из Информации Мира",
|
||||
"Exporting a character would also export the selected Lorebook file embedded in the JSON data.": "Скачивание этого персонажа так же будет включать в себя закрепленный за ним Сведения",
|
||||
"Additional Lorebooks": "Вспомогательные Сведения",
|
||||
"Associate one or more auxillary Lorebooks with this character.": "Закрепить этот или еще больше вспомогательных Сведений за этим персонажем",
|
||||
"NOTE: These choices are optional and won't be preserved on character export!": "ВНИМАНИЕ: Эти выборы необязательные и не будут сохранены при экспорте персонажа",
|
||||
"Rename chat file": "Переименовать чат",
|
||||
"Export JSONL chat file": "Создать чат в форме JSONL",
|
||||
"Download chat as plain text document": "Скачать чат в формте .txt",
|
||||
"Delete chat file": "Удалить файл этого чата",
|
||||
"Delete tag": "Удалить тэг",
|
||||
"Translate message": "Перевести сообщение",
|
||||
"Generate Image": "Создать изображение",
|
||||
"Narrate": "Повествовать",
|
||||
"Prompt": "Промт",
|
||||
"Create Bookmark": "Создать закладку",
|
||||
"Copy": "Скопировать",
|
||||
"Open bookmark chat": "Открыть чат из закладки",
|
||||
"Confirm": "Подтвердить",
|
||||
"Copy this message": "Скопировать это сообщение",
|
||||
"Delete this message": "Удалить это сообщение",
|
||||
"Move message up": "Переместить сообщение вверх",
|
||||
"Move message down": "Переместить сообщение вниз",
|
||||
"Enlarge": "Увеличить",
|
||||
"Temporarily disable automatic replies from this character": "Временно отключить сообщения от этого персонажа",
|
||||
"Enable automatic replies from this character": "Включить автоматическую отправку сообщения этого персонажа",
|
||||
"Trigger a message from this character": "Активировать сообщение этого персонажа",
|
||||
"Move up": "Переместить вверх",
|
||||
"Move down": "Переместить вниз",
|
||||
"View character card": "Посмотреть карточку персонажа",
|
||||
"Remove from group": "Убрать из группы",
|
||||
"Add to group": "Добавить в группу",
|
||||
"Add": "Добавить",
|
||||
"Abort request": "Прекратить генерацию",
|
||||
"Send a message": "отправить сообщение",
|
||||
"Ask AI to write your message for you": "ИИ напишет сообщение за вас",
|
||||
"Continue the last message": "Продолжить текущее сообщение",
|
||||
"Bind user name to that avatar": "Закрепить имя за этой личностью",
|
||||
"Select this as default persona for the new chats.": "Выбрать эту как стартовую личность",
|
||||
"Change persona image": "Сменить изображение личности",
|
||||
"Delete persona": "Удалить личность"
|
||||
}
|
||||
}
|
||||
|
20
public/img/ai21.svg
Normal file
20
public/img/ai21.svg
Normal file
@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="" height="" viewBox="0 0 60 15" fill="none"
|
||||
data-inject-url="http://127.0.0.1:8001/img/ai21.svg" class="icon-svg timestamp-icon">
|
||||
<g clip-path="url(#clip0--inject-2)">
|
||||
<path
|
||||
d="M16.6207 23.4327L15.7407 20.6609H7.62075L6.74035 23.4327H1.10132L9.25303 0.609131H14.1115L22.361 23.4327H16.6207ZM11.6987 7.12926L8.89437 16.52H14.4377L11.6987 7.12926Z"
|
||||
fill=""></path>
|
||||
<path d="M22.9893 0.609131H28.3367V23.4327H22.9893V0.609131Z" fill=""></path>
|
||||
<path
|
||||
d="M29.778 18.7697C30.0181 17.6428 30.4422 16.5632 31.0333 15.5743C31.5474 14.7385 32.1914 13.9901 32.941 13.357C33.7068 12.7218 34.5187 12.1443 35.37 11.6294C36.0436 11.2378 36.6685 10.8627 37.2449 10.5043C37.7764 10.178 38.2832 9.81296 38.7611 9.4121C39.1738 9.06912 39.5217 8.65497 39.7883 8.18927C40.0429 7.72502 40.1721 7.20247 40.1632 6.67309C40.1632 5.7602 39.9133 5.10266 39.4134 4.70047C38.8833 4.2885 38.2258 4.07506 37.5548 4.09717C36.7912 4.07408 36.0486 4.34892 35.4841 4.86356C34.9298 5.37448 34.6527 6.2277 34.6527 7.42323H29.4028C29.3934 6.43009 29.5762 5.44452 29.9411 4.52079C30.291 3.64199 30.831 2.85144 31.5222 2.20575C32.2584 1.52916 33.1235 1.00796 34.0657 0.673329C35.1756 0.285645 36.3455 0.0978785 37.5209 0.118755C38.5297 0.114098 39.5341 0.25135 40.5046 0.526479C41.4073 0.777025 42.2539 1.19757 42.999 1.76555C43.731 2.33806 44.3175 3.07539 44.7107 3.9175C45.1549 4.89419 45.3723 5.95876 45.3465 7.0314C45.3582 7.93876 45.1454 8.83495 44.7269 9.64014C44.3262 10.406 43.8154 11.109 43.2108 11.7268C42.6204 12.3291 41.9705 12.87 41.271 13.3411C40.5746 13.8087 39.9441 14.2054 39.3795 14.5311C38.5964 15.0529 37.9496 15.504 37.4394 15.8845C37.0016 16.2011 36.5925 16.5556 36.2169 16.9439C35.9359 17.2322 35.7191 17.5768 35.5808 17.9549C35.4549 18.3444 35.3943 18.752 35.4015 19.1612H45.1837V23.4326H29.3378C29.3099 21.8667 29.4575 20.3027 29.778 18.7697Z"
|
||||
fill=""></path>
|
||||
<path
|
||||
d="M46.1938 4.61724C47.1075 4.63586 48.0205 4.55382 48.9163 4.37261C49.5146 4.25901 50.0784 4.00788 50.5631 3.63905C50.952 3.31635 51.2358 2.88492 51.3782 2.39998C51.5411 1.81652 51.618 1.21237 51.6062 0.606689H56.2708V23.4327H50.891V8.33548H46.1938V4.61724Z"
|
||||
fill=""></path>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0--inject-2">
|
||||
<rect width="117.818" height="24" fill="white" transform="translate(1.09106)"></rect>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
@ -22,29 +22,32 @@
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="img/apple-icon-114x114.png" />
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="img/apple-icon-144x144.png" />
|
||||
|
||||
<script src="scripts/jquery-3.5.1.min.js"></script>
|
||||
<script src="scripts/jquery-ui.min.js"></script>
|
||||
<script src="scripts/jquery.transit.min.js"></script>
|
||||
<script src="scripts/jquery-cookie-1.4.1.min.js"></script>
|
||||
<script src="scripts/jquery.ui.touch-punch.min.js"></script>
|
||||
<script src="scripts/showdown.min.js"></script>
|
||||
<script src="scripts/showdown-katex.min.js"></script>
|
||||
<script src="scripts/popper.js"></script>
|
||||
<script src="scripts/purify.min.js"></script>
|
||||
<script src="scripts/highlight.min.js"></script>
|
||||
<script src="scripts/moment.min.js"></script>
|
||||
<script src="scripts/cropper.min.js"></script>
|
||||
<script src="scripts/jquery-cropper.min.js"></script>
|
||||
<script src="scripts/toastr.min.js"></script>
|
||||
<script src="scripts/fuse.js"></script>
|
||||
<script src="scripts/select2.min.js"></script>
|
||||
<script src="scripts/seedrandom.min.js"></script>
|
||||
<script src="scripts/droll.js"></script>
|
||||
<script src="scripts/localforage.min.js"></script>
|
||||
<script src="scripts/handlebars.js"></script>
|
||||
<script type="module" src="scripts/eventemitter.js"></script>
|
||||
<script src="lib/jquery-3.5.1.min.js"></script>
|
||||
<script src="lib/jquery-ui.min.js"></script>
|
||||
<script src="lib/jquery.transit.min.js"></script>
|
||||
<script src="lib/jquery-cookie-1.4.1.min.js"></script>
|
||||
<script src="lib/jquery.ui.touch-punch.min.js"></script>
|
||||
<script src="lib/showdown.min.js"></script>
|
||||
<script src="lib/showdown-katex.min.js"></script>
|
||||
<script src="lib/popper.js"></script>
|
||||
<script src="lib/purify.min.js"></script>
|
||||
<script src="lib/highlight.min.js"></script>
|
||||
<script src="lib/moment.min.js"></script>
|
||||
<script src="lib/cropper.min.js"></script>
|
||||
<script src="lib/jquery-cropper.min.js"></script>
|
||||
<script src="lib/toastr.min.js"></script>
|
||||
<script src="lib/fuse.js"></script>
|
||||
<script src="lib/select2.min.js"></script>
|
||||
<script src="lib/seedrandom.min.js"></script>
|
||||
<script src="lib/droll.js"></script>
|
||||
<script src="lib/localforage.min.js"></script>
|
||||
<script src="lib/handlebars.js"></script>
|
||||
<script src="lib/pagination.js"></script>
|
||||
<script src="lib/toolcool-color-picker.js"></script>
|
||||
<script src="lib/svg-inject.js"></script>
|
||||
<script type="module" src="lib/swiped-events.js"></script>
|
||||
<script type="module" src="lib/eventemitter.js"></script>
|
||||
<script type="module" src="scripts/power-user.js"></script>
|
||||
<script type="module" src="scripts/swiped-events.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
<link rel="stylesheet" href="css/bg_load.css">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
@ -98,7 +101,7 @@
|
||||
<script type="module" src="scripts/extensions.js"></script>
|
||||
<script type="module" src="scripts/authors-note.js"></script>
|
||||
<script type="module" src="scripts/preset-manager.js"></script>
|
||||
<script type="text/javascript" src="scripts/toolcool-color-picker.js"></script>
|
||||
<script type="module" src="scripts/filters.js"></script>
|
||||
|
||||
<title>SillyTavern</title>
|
||||
</head>
|
||||
@ -148,8 +151,8 @@
|
||||
</select>
|
||||
<i data-preset-manager-update="kobold" class="menu_button fa-solid fa-save" title="Update current preset" data-i18n="[title]Update current preset"></i>
|
||||
<i data-preset-manager-new="kobold" class="menu_button fa-solid fa-plus" title="Create new preset" data-i18n="[title]Create new preset"></i>
|
||||
<i data-preset-manager-import="kobold" class="menu_button fa-solid fa-upload" title="Import preset" data-i18n="[title]Import preset"></i>
|
||||
<i data-preset-manager-export="kobold" class="menu_button fa-solid fa-download" title="Export preset" data-i18n="[title]Export preset"></i>
|
||||
<i data-preset-manager-import="kobold" class="menu_button fa-solid fa-file-import" title="Import preset" data-i18n="[title]Import preset"></i>
|
||||
<i data-preset-manager-export="kobold" class="menu_button fa-solid fa-file-export" title="Export preset" data-i18n="[title]Export preset"></i>
|
||||
<i data-preset-manager-delete="kobold" class="menu_button fa-solid fa-trash-can" title="Delete the preset" data-i18n="[title]Delete the preset"></i>
|
||||
</div>
|
||||
</div>
|
||||
@ -166,8 +169,8 @@
|
||||
</select>
|
||||
<i data-preset-manager-update="novel" class="menu_button fa-solid fa-save" title="Update current preset" data-i18n="[title]Update current preset"></i>
|
||||
<i data-preset-manager-new="novel" class="menu_button fa-solid fa-plus" title="Create new preset" data-i18n="[title]Create new preset"></i>
|
||||
<i data-preset-manager-import="novel" class="menu_button fa-solid fa-upload" title="Import preset" data-i18n="[title]Import preset"></i>
|
||||
<i data-preset-manager-export="novel" class="menu_button fa-solid fa-download" title="Export preset" data-i18n="[title]Export preset"></i>
|
||||
<i data-preset-manager-import="novel" class="menu_button fa-solid fa-file-import" title="Import preset" data-i18n="[title]Import preset"></i>
|
||||
<i data-preset-manager-export="novel" class="menu_button fa-solid fa-file-export" title="Export preset" data-i18n="[title]Export preset"></i>
|
||||
<i data-preset-manager-delete="novel" class="menu_button fa-solid fa-trash-can" title="Delete the preset" data-i18n="[title]Delete the preset"></i>
|
||||
</div>
|
||||
</div>
|
||||
@ -180,8 +183,8 @@
|
||||
</select>
|
||||
<i id="update_oai_preset" class="menu_button fa-solid fa-save" title="Update current preset" data-i18n="[title]Update current preset"></i>
|
||||
<i id="new_oai_preset" class="menu_button fa-solid fa-plus" title="Create new preset" data-i18n="[title]Create new preset"></i>
|
||||
<i title="Import preset" id="import_oai_preset" class="menu_button fa-solid fa-upload" data-i18n="[title]Import preset"></i>
|
||||
<i title="Export preset" id="export_oai_preset" class="menu_button fa-solid fa-download" data-i18n="[title]Export preset"></i>
|
||||
<i title="Import preset" id="import_oai_preset" class="menu_button fa-solid fa-file-import" data-i18n="[title]Import preset"></i>
|
||||
<i title="Export preset" id="export_oai_preset" class="menu_button fa-solid fa-file-export" data-i18n="[title]Export preset"></i>
|
||||
<i id="delete_oai_preset" class="menu_button fa-solid fa-trash-can" title="Delete the preset" data-i18n="[title]Delete the preset"></i>
|
||||
<input id="openai_preset_import_file" type="file" accept=".json,.settings" hidden />
|
||||
</div>
|
||||
@ -195,8 +198,8 @@
|
||||
</select>
|
||||
<i data-preset-manager-update="textgenerationwebui" class="menu_button fa-solid fa-save" title="Update current preset" data-i18n="[title]Update current preset"></i>
|
||||
<i data-preset-manager-new="textgenerationwebui" class="menu_button fa-solid fa-plus" title="Create new preset" data-i18n="[title]Create new preset"></i>
|
||||
<i data-preset-manager-import="textgenerationwebui" class="menu_button fa-solid fa-upload" title="Import preset" data-i18n="[title]Import preset"></i>
|
||||
<i data-preset-manager-export="textgenerationwebui" class="menu_button fa-solid fa-download" title="Export preset" data-i18n="[title]Export preset"></i>
|
||||
<i data-preset-manager-import="textgenerationwebui" class="menu_button fa-solid fa-file-import" title="Import preset" data-i18n="[title]Import preset"></i>
|
||||
<i data-preset-manager-export="textgenerationwebui" class="menu_button fa-solid fa-file-export" title="Export preset" data-i18n="[title]Export preset"></i>
|
||||
<i data-preset-manager-delete="textgenerationwebui" class="menu_button fa-solid fa-trash-can" title="Delete the preset" data-i18n="[title]Delete the preset"></i>
|
||||
</div>
|
||||
</div>
|
||||
@ -652,7 +655,7 @@
|
||||
Max prompt cost: <span id="openrouter_max_prompt_cost">Unknown</span>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="range-block" data-source="openai,claude,windowai,openrouter">
|
||||
<div class="range-block" data-source="openai,claude,windowai,openrouter,ai21">
|
||||
<div class="range-block-title" data-i18n="Temperature">
|
||||
Temperature
|
||||
</div>
|
||||
@ -667,7 +670,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,openrouter">
|
||||
<div class="range-block" data-source="openai,openrouter,ai21">
|
||||
<div class="range-block-title" data-i18n="Frequency Penalty">
|
||||
Frequency Penalty
|
||||
</div>
|
||||
@ -682,7 +685,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,openrouter">
|
||||
<div class="range-block" data-source="openai,openrouter,ai21">
|
||||
<div class="range-block-title" data-i18n="Presence Penalty">
|
||||
Presence Penalty
|
||||
</div>
|
||||
@ -697,7 +700,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="claude,openrouter">
|
||||
<div class="range-block" data-source="ai21">
|
||||
<div class="range-block-title" data-i18n="Count Penalty">
|
||||
Count Penalty
|
||||
</div>
|
||||
<div class="range-block-range-and-counter">
|
||||
<div class="range-block-range">
|
||||
<input type="range" id="count_pen" name="volume" min="0" max="1" step="0.01">
|
||||
</div>
|
||||
<div class="range-block-counter">
|
||||
<div contenteditable="true" data-for="count_pen" id="count_pen_counter">
|
||||
select
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="claude,openrouter,ai21">
|
||||
<div class="range-block-title" data-i18n="Top K">
|
||||
Top K
|
||||
</div>
|
||||
@ -712,7 +730,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,claude,openrouter">
|
||||
<div class="range-block" data-source="openai,claude,openrouter,ai21">
|
||||
<div class="range-block-title" data-i18n="Top-p">
|
||||
Top P
|
||||
</div>
|
||||
@ -1416,6 +1434,14 @@
|
||||
<span data-i18n="May help the model to understand context. Names must only contain letters or numbers.">Helps the model to associate messages in group chats. Names must only contain letters or numbers without whitespaces.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="ai21">
|
||||
<label for="use_ai21_tokenizer" title="Use AI21 Tokenizer" class="checkbox_label widthFreeExpand">
|
||||
<input id="use_ai21_tokenizer" type="checkbox" /><span data-i18n="Use AI21 Tokenizer">Use AI21 Tokenizer</span>
|
||||
</label>
|
||||
<div class="toggle-description justifyLeft">
|
||||
<span data-i18n="Use the appropriate tokenizer for Jurassic models, which is more efficient than GPT's.">Use the appropriate tokenizer for Jurassic models, which is more efficient than GPT's.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline-drawer m-t-1 wide100p">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b data-i18n="Quick Edit">Quick Edit</b>
|
||||
@ -1575,8 +1601,8 @@
|
||||
<select id="openai_logit_bias_preset">
|
||||
</select>
|
||||
<i title="New preset" id="openai_logit_bias_new_preset" class="menu_button fa-solid fa-plus" data-i18n="[title]New preset"></i>
|
||||
<i title="Import preset" id="openai_logit_bias_import_preset" class="menu_button fa-solid fa-upload" data-i18n="[title]Import preset"></i>
|
||||
<i title="Export preset" id="openai_logit_bias_export_preset" class="menu_button fa-solid fa-download" data-i18n="[title]Export preset"></i>
|
||||
<i title="Import preset" id="openai_logit_bias_import_preset" class="menu_button fa-solid fa-file-import" data-i18n="[title]Import preset"></i>
|
||||
<i title="Export preset" id="openai_logit_bias_export_preset" class="menu_button fa-solid fa-file-export" data-i18n="[title]Export preset"></i>
|
||||
<i title="Delete preset" id="openai_logit_bias_delete_preset" class="menu_button fa-solid fa-trash-can" data-i18n="[title]Delete preset"></i>
|
||||
<input id="openai_logit_bias_import_file" type="file" accept=".json" hidden />
|
||||
</div>
|
||||
@ -1616,7 +1642,7 @@
|
||||
<option value="koboldhorde"><span data-i18n="KoboldAI Horde">KoboldAI Horde</span></option>
|
||||
<option value="textgenerationwebui"><span data-i18n="Text Gen WebUI (ooba/Mancer)">Text Gen WebUI (ooba/Mancer)</span></option>
|
||||
<option value="novel"><span data-i18n="NovelAI">NovelAI</span></option>
|
||||
<option value="openai"><span data-i18n="Chat Completion (OpenAI, Claude, Window/OpenRouter, Scale)">Chat Completion (OpenAI, Claude, Window, OpenRouter, Scale)</span></option>
|
||||
<option value="openai"><span data-i18n="Chat Completion (OpenAI, Claude, Window/OpenRouter, Scale, AI21)">Chat Completion (OpenAI, Claude, Window, OpenRouter, Scale, AI21)</span></option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="kobold_horde" style="position: relative;"> <!-- shows the kobold settings -->
|
||||
@ -1802,6 +1828,7 @@
|
||||
<option value="openrouter">OpenRouter</option>
|
||||
<option value="claude">Claude</option>
|
||||
<option value="scale">Scale</option>
|
||||
<option value="ai21">AI21</option>
|
||||
</select>
|
||||
<form id="openai_form" data-source="openai" action="javascript:void(null);" method="post" enctype="multipart/form-data">
|
||||
<h4><span data-i18n="OpenAI API key">OpenAI API key</span></h4>
|
||||
@ -1970,6 +1997,27 @@
|
||||
<select id="model_scale_select" class="displayNone"></select>
|
||||
</form>
|
||||
|
||||
<form id="ai21_form" data-source="ai21" action="javascript:void(null);" method="post" enctype="multipart/form-data">
|
||||
<h4>AI21 API Key</h4>
|
||||
<div class="flex-container">
|
||||
<input id="api_key_ai21" name="api_key_ai21" class="text_pole flex1" maxlength="500" value="" type="text" autocomplete="off">
|
||||
<div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_ai21"></div>
|
||||
</div>
|
||||
<div data-for="api_key_ai21" class="neutral_warning">
|
||||
For privacy reasons, your API key will be hidden after you reload the page.
|
||||
</div>
|
||||
<div>
|
||||
<h4 data-i18n="AI21 Model">AI21 Model</h4>
|
||||
<select id="model_ai21_select">
|
||||
<optgroup label="Latest">
|
||||
<option value="j2-ultra">j2-ultra</option>
|
||||
<option value="j2-mid">j2-mid</option>
|
||||
<option value="j2-light">j2-light</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="flex-container flex">
|
||||
<input id="api_button_openai" class="menu_button" type="submit" value="Connect">
|
||||
<input data-source="openrouter" id="openrouter_authorize" class="menu_button" type="button" value="Authorize" title="Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai" data-i18n="[title]Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai">
|
||||
@ -2013,14 +2061,14 @@
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="context_story_string" data-i18n="Story String">
|
||||
Story String
|
||||
<label for="context_story_string">
|
||||
<small data-i18n="Story String">Story String</small>
|
||||
</label>
|
||||
<textarea id="context_story_string" class="text_pole textarea_compact" rows="3"></textarea>
|
||||
<div class="flex-container">
|
||||
<div class="flex1">
|
||||
<label for="context_example_separator">
|
||||
<span data-i18n="Example Separator">Example Separator</span>
|
||||
<small data-i18n="Example Separator">Example Separator</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="context_example_separator" class="text_pole textarea_compact" maxlength="500" rows="1"></textarea>
|
||||
@ -2028,7 +2076,7 @@
|
||||
</div>
|
||||
<div class="flex1">
|
||||
<label for="context_chat_start">
|
||||
<span data-i18n="Chat Start">Chat Start</span>
|
||||
<small data-i18n="Chat Start">Chat Start</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="context_chat_start" class="text_pole textarea_compact" maxlength="500" rows="1"></textarea>
|
||||
@ -2068,9 +2116,23 @@
|
||||
<label for="instruct_presets">
|
||||
<span data-i18n="Presets">Presets</span>
|
||||
</label>
|
||||
<select id="instruct_presets"></select>
|
||||
<div class="flex-container">
|
||||
<select id="instruct_presets" class="flex1 margin0"></select>
|
||||
<div id="instruct_set_default" class="menu_button menu_button_icon margin0">
|
||||
<i class="fa-solid fa-xs fa-fw fa-heart"></i>
|
||||
<span data-i18n="Default">Default</span>
|
||||
</div>
|
||||
</div>
|
||||
<label>
|
||||
<span data-i18n="System Prompt">System Prompt</span>
|
||||
<small data-i18n="Activation Regex">
|
||||
Activation Regex
|
||||
</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="instruct_activation_regex" class="text_pole textarea_compact" maxlength="5000" rows="1"></textarea>
|
||||
</div>
|
||||
<label>
|
||||
<small data-i18n="System Prompt">System Prompt</small>
|
||||
</label>
|
||||
<div class="prompt_overridden">
|
||||
Overridden by the Character Definitions.
|
||||
@ -2222,7 +2284,7 @@
|
||||
</div>
|
||||
</h4>
|
||||
<div>
|
||||
<textarea id="custom_stopping_strings" rows="2" class="text_pole textarea_compact" placeholder="["Ford", "BMW", "Fiat"]"></textarea>
|
||||
<textarea id="custom_stopping_strings" rows="2" class="text_pole textarea_compact monospace" placeholder="["Ford", "BMW", "Fiat"]"></textarea>
|
||||
</div>
|
||||
<label class="checkbox_label" for="custom_stopping_strings_macro">
|
||||
<input id="custom_stopping_strings_macro" type="checkbox" checked>
|
||||
@ -2988,7 +3050,7 @@
|
||||
<div id="rm_button_selected_ch">
|
||||
<h2></h2>
|
||||
</div>
|
||||
<i id="hideCharPanelAvatarButton" class="fa-solid fa-eye menu_button"></i>
|
||||
<i id="hideCharPanelAvatarButton" class="fa-solid fa-eye right_menu_button"></i>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end group peeking cope structure-->
|
||||
@ -3199,6 +3261,7 @@
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<div name="Current Group Members" class="flex-container flexFlowColumn overflowYAuto flex1">
|
||||
<div id="rm_group_members_pagination" class="group_pagination"></div>
|
||||
<div id="rm_group_members" class="overflowYAuto flex-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
@ -3211,11 +3274,12 @@
|
||||
<div class="inline-drawer-content">
|
||||
<div name="Unadded Char List" class="flex-container flexFlowColumn overflowYAuto flex1">
|
||||
<div id="rm_group_add_members_header">
|
||||
<input id="rm_group_filter" class="text_pole margin0" type="search" data-i18n="[placeholder]Filter..." placeholder="Filter..." maxlength="100" />
|
||||
<input id="rm_group_filter" class="text_pole margin0" type="search" data-i18n="[placeholder]Search..." placeholder="Search..." maxlength="100" />
|
||||
</div>
|
||||
<div class="rm_tag_controls">
|
||||
<div class="tags rm_tag_filter"></div>
|
||||
</div>
|
||||
<div id="rm_group_add_members_pagination" class="group_pagination"></div>
|
||||
<div id="rm_group_add_members" class="overflowYAuto flex-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
@ -3234,7 +3298,7 @@
|
||||
<div id="charListFixedTop">
|
||||
<form id="form_character_search_form" action="javascript:void(null);">
|
||||
<div id="rm_button_create" title="Create New Character" data-i18n="[title]Create New Character" class="menu_button fa-solid fa-user-plus "></div>
|
||||
<div id="character_import_button" title="Import Character from File" data-i18n="[title]Import Character from File" class="menu_button fa-solid fa-file-arrow-up faSmallFontSquareFix"></div>
|
||||
<div id="character_import_button" title="Import Character from File" data-i18n="[title]Import Character from File" class="menu_button fa-solid fa-file-import faSmallFontSquareFix"></div>
|
||||
<div id="external_import_button" title="Import content from external URL" data-i18n="[title]Import content from external URL" class="menu_button fa-solid fa-cloud-arrow-down faSmallFontSquareFix"></div>
|
||||
<div id="rm_button_group_chats" title="Create New Chat Group" data-i18n="[title]Create New Chat Group" class="menu_button fa-solid fa-users-gear "></div>
|
||||
<input id="character_search_bar" class="text_pole width100p" type="search" data-i18n="[placeholder]Search..." placeholder="Search..." maxlength="50" />
|
||||
@ -3255,26 +3319,14 @@
|
||||
<div class="rm_tag_controls">
|
||||
<div class="tags rm_tag_filter"></div>
|
||||
</div>
|
||||
<!-- a div containing a dynamically updated count of characters currently displayed -->
|
||||
<div class="flex-container alignitemscenter">
|
||||
<div id="rm_character_count"></div>
|
||||
<i id="charListGridToggle" class="fa-solid fa-table-cells-large menu_button" title="Toggle character grid view"></i>
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<div id="rm_print_characters_pagination">
|
||||
<i id="charListGridToggle" class="fa-solid fa-table-cells-large menu_button" title="Toggle character grid view"></i>
|
||||
</div>
|
||||
<div id="rm_print_characters_block" class="flexFlowColumn"></div>
|
||||
</div>
|
||||
|
||||
<!-- only displays after creating/deleting a character/group -->
|
||||
<div id="rm_info_block" class="right_menu">
|
||||
<div id="rm_info_panel">
|
||||
<div id="rm_info_avatar"></div>
|
||||
<div id="rm_info_text"></div>
|
||||
<div id="rm_info_button" class="menu_button" data-i18n="Back">Back</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@ -3401,8 +3453,9 @@
|
||||
|
||||
<div id="shadow_select_chat_popup">
|
||||
<div id="select_chat_popup">
|
||||
<input type="text" id="select_chat_search" placeholder="Search..." autocomplete="off">
|
||||
<div id="select_chat_import"> <!-- import chat popup header -->
|
||||
<div id="chat_import_button" class="fa-solid fa-file-arrow-up menu_button"></div>
|
||||
<div id="chat_import_button" class="fa-solid fa-file-import menu_button"></div>
|
||||
<div id="selectChatPopupHeaderText" class="TxtLrgBoldCenter">
|
||||
<span id="ChatHistoryCharName"></span>
|
||||
<br>
|
||||
|
@ -1,13 +1,13 @@
|
||||
{
|
||||
"input_sequence": "### Instruction:",
|
||||
"last_output_sequence": "### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):",
|
||||
"macro": true,
|
||||
"name": "Roleplay",
|
||||
"names": true,
|
||||
"last_output_sequence": "### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):",
|
||||
"output_sequence": "### Response:",
|
||||
"separator_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"system_prompt": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.\n\nAvoid repetition, don't loop. Develop the plot slowly, always stay in character. Describe all actions in full, elaborate, explicit, graphic, and vivid detail. Mention all relevant sensory perceptions.\n\n### Input:",
|
||||
"system_prompt": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.\n\nAvoid repetition, don't loop. Develop the plot slowly, always stay in character. Describe all actions in full, elaborate, explicit, graphic, and vivid detail. Mention all relevant sensory perceptions.",
|
||||
"system_sequence": "",
|
||||
"wrap": true
|
||||
}
|
||||
|
13
public/instruct/simple-proxy-for-tavern.json
Normal file
13
public/instruct/simple-proxy-for-tavern.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"input_sequence": "### Instruction:\n#### {{user}}:",
|
||||
"last_output_sequence": "### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):\n#### {{char}}:",
|
||||
"macro": true,
|
||||
"name": "simple-proxy-for-tavern",
|
||||
"names": false,
|
||||
"output_sequence": "### Response:\n#### {{char}}:",
|
||||
"separator_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"system_prompt": "## {{char}}\n- You're \"{{char}}\" in this never-ending roleplay with \"{{user}}\".",
|
||||
"system_sequence": "",
|
||||
"wrap": true
|
||||
}
|
1190
public/lib/pagination.js
Normal file
1190
public/lib/pagination.js
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
697
public/lib/svg-inject.js
Normal file
697
public/lib/svg-inject.js
Normal file
@ -0,0 +1,697 @@
|
||||
/**
|
||||
* SVGInject - Version 1.2.3
|
||||
* A tiny, intuitive, robust, caching solution for injecting SVG files inline into the DOM.
|
||||
*
|
||||
* https://github.com/iconfu/svg-inject
|
||||
*
|
||||
* Copyright (c) 2018 INCORS, the creators of iconfu.com
|
||||
* @license MIT License - https://github.com/iconfu/svg-inject/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
(function(window, document) {
|
||||
// constants for better minification
|
||||
var _CREATE_ELEMENT_ = 'createElement';
|
||||
var _GET_ELEMENTS_BY_TAG_NAME_ = 'getElementsByTagName';
|
||||
var _LENGTH_ = 'length';
|
||||
var _STYLE_ = 'style';
|
||||
var _TITLE_ = 'title';
|
||||
var _UNDEFINED_ = 'undefined';
|
||||
var _SET_ATTRIBUTE_ = 'setAttribute';
|
||||
var _GET_ATTRIBUTE_ = 'getAttribute';
|
||||
|
||||
var NULL = null;
|
||||
|
||||
// constants
|
||||
var __SVGINJECT = '__svgInject';
|
||||
var ID_SUFFIX = '--inject-';
|
||||
var ID_SUFFIX_REGEX = new RegExp(ID_SUFFIX + '\\d+', "g");
|
||||
var LOAD_FAIL = 'LOAD_FAIL';
|
||||
var SVG_NOT_SUPPORTED = 'SVG_NOT_SUPPORTED';
|
||||
var SVG_INVALID = 'SVG_INVALID';
|
||||
var ATTRIBUTE_EXCLUSION_NAMES = ['src', 'alt', 'onload', 'onerror'];
|
||||
var A_ELEMENT = document[_CREATE_ELEMENT_]('a');
|
||||
var IS_SVG_SUPPORTED = typeof SVGRect != _UNDEFINED_;
|
||||
var DEFAULT_OPTIONS = {
|
||||
useCache: true,
|
||||
copyAttributes: true,
|
||||
makeIdsUnique: true
|
||||
};
|
||||
// Map of IRI referenceable tag names to properties that can reference them. This is defined in
|
||||
// https://www.w3.org/TR/SVG11/linking.html#processingIRI
|
||||
var IRI_TAG_PROPERTIES_MAP = {
|
||||
clipPath: ['clip-path'],
|
||||
'color-profile': NULL,
|
||||
cursor: NULL,
|
||||
filter: NULL,
|
||||
linearGradient: ['fill', 'stroke'],
|
||||
marker: ['marker', 'marker-end', 'marker-mid', 'marker-start'],
|
||||
mask: NULL,
|
||||
pattern: ['fill', 'stroke'],
|
||||
radialGradient: ['fill', 'stroke']
|
||||
};
|
||||
var INJECTED = 1;
|
||||
var FAIL = 2;
|
||||
|
||||
var uniqueIdCounter = 1;
|
||||
var xmlSerializer;
|
||||
var domParser;
|
||||
|
||||
|
||||
// creates an SVG document from an SVG string
|
||||
function svgStringToSvgDoc(svgStr) {
|
||||
domParser = domParser || new DOMParser();
|
||||
return domParser.parseFromString(svgStr, 'text/xml');
|
||||
}
|
||||
|
||||
|
||||
// searializes an SVG element to an SVG string
|
||||
function svgElemToSvgString(svgElement) {
|
||||
xmlSerializer = xmlSerializer || new XMLSerializer();
|
||||
return xmlSerializer.serializeToString(svgElement);
|
||||
}
|
||||
|
||||
|
||||
// Returns the absolute url for the specified url
|
||||
function getAbsoluteUrl(url) {
|
||||
A_ELEMENT.href = url;
|
||||
return A_ELEMENT.href;
|
||||
}
|
||||
|
||||
|
||||
// Load svg with an XHR request
|
||||
function loadSvg(url, callback, errorCallback) {
|
||||
if (url) {
|
||||
var req = new XMLHttpRequest();
|
||||
req.onreadystatechange = function() {
|
||||
if (req.readyState == 4) {
|
||||
// readyState is DONE
|
||||
var status = req.status;
|
||||
if (status == 200) {
|
||||
// request status is OK
|
||||
callback(req.responseXML, req.responseText.trim());
|
||||
} else if (status >= 400) {
|
||||
// request status is error (4xx or 5xx)
|
||||
errorCallback();
|
||||
} else if (status == 0) {
|
||||
// request status 0 can indicate a failed cross-domain call
|
||||
errorCallback();
|
||||
}
|
||||
}
|
||||
};
|
||||
req.open('GET', url, true);
|
||||
req.send();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Copy attributes from img element to svg element
|
||||
function copyAttributes(imgElem, svgElem) {
|
||||
var attribute;
|
||||
var attributeName;
|
||||
var attributeValue;
|
||||
var attributes = imgElem.attributes;
|
||||
for (var i = 0; i < attributes[_LENGTH_]; i++) {
|
||||
attribute = attributes[i];
|
||||
attributeName = attribute.name;
|
||||
// Only copy attributes not explicitly excluded from copying
|
||||
if (ATTRIBUTE_EXCLUSION_NAMES.indexOf(attributeName) == -1) {
|
||||
attributeValue = attribute.value;
|
||||
// If img attribute is "title", insert a title element into SVG element
|
||||
if (attributeName == _TITLE_) {
|
||||
var titleElem;
|
||||
var firstElementChild = svgElem.firstElementChild;
|
||||
if (firstElementChild && firstElementChild.localName.toLowerCase() == _TITLE_) {
|
||||
// If the SVG element's first child is a title element, keep it as the title element
|
||||
titleElem = firstElementChild;
|
||||
} else {
|
||||
// If the SVG element's first child element is not a title element, create a new title
|
||||
// ele,emt and set it as the first child
|
||||
titleElem = document[_CREATE_ELEMENT_ + 'NS']('http://www.w3.org/2000/svg', _TITLE_);
|
||||
svgElem.insertBefore(titleElem, firstElementChild);
|
||||
}
|
||||
// Set new title content
|
||||
titleElem.textContent = attributeValue;
|
||||
} else {
|
||||
// Set img attribute to svg element
|
||||
svgElem[_SET_ATTRIBUTE_](attributeName, attributeValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// This function appends a suffix to IDs of referenced elements in the <defs> in order to to avoid ID collision
|
||||
// between multiple injected SVGs. The suffix has the form "--inject-X", where X is a running number which is
|
||||
// incremented with each injection. References to the IDs are adjusted accordingly.
|
||||
// We assume tha all IDs within the injected SVG are unique, therefore the same suffix can be used for all IDs of one
|
||||
// injected SVG.
|
||||
// If the onlyReferenced argument is set to true, only those IDs will be made unique that are referenced from within the SVG
|
||||
function makeIdsUnique(svgElem, onlyReferenced) {
|
||||
var idSuffix = ID_SUFFIX + uniqueIdCounter++;
|
||||
// Regular expression for functional notations of an IRI references. This will find occurences in the form
|
||||
// url(#anyId) or url("#anyId") (for Internet Explorer) and capture the referenced ID
|
||||
var funcIriRegex = /url\("?#([a-zA-Z][\w:.-]*)"?\)/g;
|
||||
// Get all elements with an ID. The SVG spec recommends to put referenced elements inside <defs> elements, but
|
||||
// this is not a requirement, therefore we have to search for IDs in the whole SVG.
|
||||
var idElements = svgElem.querySelectorAll('[id]');
|
||||
var idElem;
|
||||
// An object containing referenced IDs as keys is used if only referenced IDs should be uniquified.
|
||||
// If this object does not exist, all IDs will be uniquified.
|
||||
var referencedIds = onlyReferenced ? [] : NULL;
|
||||
var tagName;
|
||||
var iriTagNames = {};
|
||||
var iriProperties = [];
|
||||
var changed = false;
|
||||
var i, j;
|
||||
|
||||
if (idElements[_LENGTH_]) {
|
||||
// Make all IDs unique by adding the ID suffix and collect all encountered tag names
|
||||
// that are IRI referenceable from properities.
|
||||
for (i = 0; i < idElements[_LENGTH_]; i++) {
|
||||
tagName = idElements[i].localName; // Use non-namespaced tag name
|
||||
// Make ID unique if tag name is IRI referenceable
|
||||
if (tagName in IRI_TAG_PROPERTIES_MAP) {
|
||||
iriTagNames[tagName] = 1;
|
||||
}
|
||||
}
|
||||
// Get all properties that are mapped to the found IRI referenceable tags
|
||||
for (tagName in iriTagNames) {
|
||||
(IRI_TAG_PROPERTIES_MAP[tagName] || [tagName]).forEach(function (mappedProperty) {
|
||||
// Add mapped properties to array of iri referencing properties.
|
||||
// Use linear search here because the number of possible entries is very small (maximum 11)
|
||||
if (iriProperties.indexOf(mappedProperty) < 0) {
|
||||
iriProperties.push(mappedProperty);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (iriProperties[_LENGTH_]) {
|
||||
// Add "style" to properties, because it may contain references in the form 'style="fill:url(#myFill)"'
|
||||
iriProperties.push(_STYLE_);
|
||||
}
|
||||
// Run through all elements of the SVG and replace IDs in references.
|
||||
// To get all descending elements, getElementsByTagName('*') seems to perform faster than querySelectorAll('*').
|
||||
// Since svgElem.getElementsByTagName('*') does not return the svg element itself, we have to handle it separately.
|
||||
var descElements = svgElem[_GET_ELEMENTS_BY_TAG_NAME_]('*');
|
||||
var element = svgElem;
|
||||
var propertyName;
|
||||
var value;
|
||||
var newValue;
|
||||
for (i = -1; element != NULL;) {
|
||||
if (element.localName == _STYLE_) {
|
||||
// If element is a style element, replace IDs in all occurences of "url(#anyId)" in text content
|
||||
value = element.textContent;
|
||||
newValue = value && value.replace(funcIriRegex, function(match, id) {
|
||||
if (referencedIds) {
|
||||
referencedIds[id] = 1;
|
||||
}
|
||||
return 'url(#' + id + idSuffix + ')';
|
||||
});
|
||||
if (newValue !== value) {
|
||||
element.textContent = newValue;
|
||||
}
|
||||
} else if (element.hasAttributes()) {
|
||||
// Run through all property names for which IDs were found
|
||||
for (j = 0; j < iriProperties[_LENGTH_]; j++) {
|
||||
propertyName = iriProperties[j];
|
||||
value = element[_GET_ATTRIBUTE_](propertyName);
|
||||
newValue = value && value.replace(funcIriRegex, function(match, id) {
|
||||
if (referencedIds) {
|
||||
referencedIds[id] = 1;
|
||||
}
|
||||
return 'url(#' + id + idSuffix + ')';
|
||||
});
|
||||
if (newValue !== value) {
|
||||
element[_SET_ATTRIBUTE_](propertyName, newValue);
|
||||
}
|
||||
}
|
||||
// Replace IDs in xlink:ref and href attributes
|
||||
['xlink:href', 'href'].forEach(function(refAttrName) {
|
||||
var iri = element[_GET_ATTRIBUTE_](refAttrName);
|
||||
if (/^\s*#/.test(iri)) { // Check if iri is non-null and internal reference
|
||||
iri = iri.trim();
|
||||
element[_SET_ATTRIBUTE_](refAttrName, iri + idSuffix);
|
||||
if (referencedIds) {
|
||||
// Add ID to referenced IDs
|
||||
referencedIds[iri.substring(1)] = 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
element = descElements[++i];
|
||||
}
|
||||
for (i = 0; i < idElements[_LENGTH_]; i++) {
|
||||
idElem = idElements[i];
|
||||
// If set of referenced IDs exists, make only referenced IDs unique,
|
||||
// otherwise make all IDs unique.
|
||||
if (!referencedIds || referencedIds[idElem.id]) {
|
||||
// Add suffix to element's ID
|
||||
idElem.id += idSuffix;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// return true if SVG element has changed
|
||||
return changed;
|
||||
}
|
||||
|
||||
|
||||
// For cached SVGs the IDs are made unique by simply replacing the already inserted unique IDs with a
|
||||
// higher ID counter. This is much more performant than a call to makeIdsUnique().
|
||||
function makeIdsUniqueCached(svgString) {
|
||||
return svgString.replace(ID_SUFFIX_REGEX, ID_SUFFIX + uniqueIdCounter++);
|
||||
}
|
||||
|
||||
|
||||
// Inject SVG by replacing the img element with the SVG element in the DOM
|
||||
function inject(imgElem, svgElem, absUrl, options) {
|
||||
if (svgElem) {
|
||||
svgElem[_SET_ATTRIBUTE_]('data-inject-url', absUrl);
|
||||
var parentNode = imgElem.parentNode;
|
||||
if (parentNode) {
|
||||
if (options.copyAttributes) {
|
||||
copyAttributes(imgElem, svgElem);
|
||||
}
|
||||
// Invoke beforeInject hook if set
|
||||
var beforeInject = options.beforeInject;
|
||||
var injectElem = (beforeInject && beforeInject(imgElem, svgElem)) || svgElem;
|
||||
// Replace img element with new element. This is the actual injection.
|
||||
parentNode.replaceChild(injectElem, imgElem);
|
||||
// Mark img element as injected
|
||||
imgElem[__SVGINJECT] = INJECTED;
|
||||
removeOnLoadAttribute(imgElem);
|
||||
// Invoke afterInject hook if set
|
||||
var afterInject = options.afterInject;
|
||||
if (afterInject) {
|
||||
afterInject(imgElem, injectElem);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
svgInvalid(imgElem, options);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Merges any number of options objects into a new object
|
||||
function mergeOptions() {
|
||||
var mergedOptions = {};
|
||||
var args = arguments;
|
||||
// Iterate over all specified options objects and add all properties to the new options object
|
||||
for (var i = 0; i < args[_LENGTH_]; i++) {
|
||||
var argument = args[i];
|
||||
for (var key in argument) {
|
||||
if (argument.hasOwnProperty(key)) {
|
||||
mergedOptions[key] = argument[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
return mergedOptions;
|
||||
}
|
||||
|
||||
|
||||
// Adds the specified CSS to the document's <head> element
|
||||
function addStyleToHead(css) {
|
||||
var head = document[_GET_ELEMENTS_BY_TAG_NAME_]('head')[0];
|
||||
if (head) {
|
||||
var style = document[_CREATE_ELEMENT_](_STYLE_);
|
||||
style.type = 'text/css';
|
||||
style.appendChild(document.createTextNode(css));
|
||||
head.appendChild(style);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Builds an SVG element from the specified SVG string
|
||||
function buildSvgElement(svgStr, verify) {
|
||||
if (verify) {
|
||||
var svgDoc;
|
||||
try {
|
||||
// Parse the SVG string with DOMParser
|
||||
svgDoc = svgStringToSvgDoc(svgStr);
|
||||
} catch(e) {
|
||||
return NULL;
|
||||
}
|
||||
if (svgDoc[_GET_ELEMENTS_BY_TAG_NAME_]('parsererror')[_LENGTH_]) {
|
||||
// DOMParser does not throw an exception, but instead puts parsererror tags in the document
|
||||
return NULL;
|
||||
}
|
||||
return svgDoc.documentElement;
|
||||
} else {
|
||||
var div = document.createElement('div');
|
||||
div.innerHTML = svgStr;
|
||||
return div.firstElementChild;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function removeOnLoadAttribute(imgElem) {
|
||||
// Remove the onload attribute. Should only be used to remove the unstyled image flash protection and
|
||||
// make the element visible, not for removing the event listener.
|
||||
imgElem.removeAttribute('onload');
|
||||
}
|
||||
|
||||
|
||||
function errorMessage(msg) {
|
||||
console.error('SVGInject: ' + msg);
|
||||
}
|
||||
|
||||
|
||||
function fail(imgElem, status, options) {
|
||||
imgElem[__SVGINJECT] = FAIL;
|
||||
if (options.onFail) {
|
||||
options.onFail(imgElem, status);
|
||||
} else {
|
||||
errorMessage(status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function svgInvalid(imgElem, options) {
|
||||
removeOnLoadAttribute(imgElem);
|
||||
fail(imgElem, SVG_INVALID, options);
|
||||
}
|
||||
|
||||
|
||||
function svgNotSupported(imgElem, options) {
|
||||
removeOnLoadAttribute(imgElem);
|
||||
fail(imgElem, SVG_NOT_SUPPORTED, options);
|
||||
}
|
||||
|
||||
|
||||
function loadFail(imgElem, options) {
|
||||
fail(imgElem, LOAD_FAIL, options);
|
||||
}
|
||||
|
||||
|
||||
function removeEventListeners(imgElem) {
|
||||
imgElem.onload = NULL;
|
||||
imgElem.onerror = NULL;
|
||||
}
|
||||
|
||||
|
||||
function imgNotSet(msg) {
|
||||
errorMessage('no img element');
|
||||
}
|
||||
|
||||
|
||||
function createSVGInject(globalName, options) {
|
||||
var defaultOptions = mergeOptions(DEFAULT_OPTIONS, options);
|
||||
var svgLoadCache = {};
|
||||
|
||||
if (IS_SVG_SUPPORTED) {
|
||||
// If the browser supports SVG, add a small stylesheet that hides the <img> elements until
|
||||
// injection is finished. This avoids showing the unstyled SVGs before style is applied.
|
||||
addStyleToHead('img[onload^="' + globalName + '("]{visibility:hidden;}');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* SVGInject
|
||||
*
|
||||
* Injects the SVG specified in the `src` attribute of the specified `img` element or array of `img`
|
||||
* elements. Returns a Promise object which resolves if all passed in `img` elements have either been
|
||||
* injected or failed to inject (Only if a global Promise object is available like in all modern browsers
|
||||
* or through a polyfill).
|
||||
*
|
||||
* Options:
|
||||
* useCache: If set to `true` the SVG will be cached using the absolute URL. Default value is `true`.
|
||||
* copyAttributes: If set to `true` the attributes will be copied from `img` to `svg`. Dfault value
|
||||
* is `true`.
|
||||
* makeIdsUnique: If set to `true` the ID of elements in the `<defs>` element that can be references by
|
||||
* property values (for example 'clipPath') are made unique by appending "--inject-X", where X is a
|
||||
* running number which increases with each injection. This is done to avoid duplicate IDs in the DOM.
|
||||
* beforeLoad: Hook before SVG is loaded. The `img` element is passed as a parameter. If the hook returns
|
||||
* a string it is used as the URL instead of the `img` element's `src` attribute.
|
||||
* afterLoad: Hook after SVG is loaded. The loaded `svg` element and `svg` string are passed as a
|
||||
* parameters. If caching is active this hook will only get called once for injected SVGs with the
|
||||
* same absolute path. Changes to the `svg` element in this hook will be applied to all injected SVGs
|
||||
* with the same absolute path. It's also possible to return an `svg` string or `svg` element which
|
||||
* will then be used for the injection.
|
||||
* beforeInject: Hook before SVG is injected. The `img` and `svg` elements are passed as parameters. If
|
||||
* any html element is returned it gets injected instead of applying the default SVG injection.
|
||||
* afterInject: Hook after SVG is injected. The `img` and `svg` elements are passed as parameters.
|
||||
* onAllFinish: Hook after all `img` elements passed to an SVGInject() call have either been injected or
|
||||
* failed to inject.
|
||||
* onFail: Hook after injection fails. The `img` element and a `status` string are passed as an parameter.
|
||||
* The `status` can be either `'SVG_NOT_SUPPORTED'` (the browser does not support SVG),
|
||||
* `'SVG_INVALID'` (the SVG is not in a valid format) or `'LOAD_FAILED'` (loading of the SVG failed).
|
||||
*
|
||||
* @param {HTMLImageElement} img - an img element or an array of img elements
|
||||
* @param {Object} [options] - optional parameter with [options](#options) for this injection.
|
||||
*/
|
||||
function SVGInject(img, options) {
|
||||
options = mergeOptions(defaultOptions, options);
|
||||
|
||||
var run = function(resolve) {
|
||||
var allFinish = function() {
|
||||
var onAllFinish = options.onAllFinish;
|
||||
if (onAllFinish) {
|
||||
onAllFinish();
|
||||
}
|
||||
resolve && resolve();
|
||||
};
|
||||
|
||||
if (img && typeof img[_LENGTH_] != _UNDEFINED_) {
|
||||
// an array like structure of img elements
|
||||
var injectIndex = 0;
|
||||
var injectCount = img[_LENGTH_];
|
||||
|
||||
if (injectCount == 0) {
|
||||
allFinish();
|
||||
} else {
|
||||
var finish = function() {
|
||||
if (++injectIndex == injectCount) {
|
||||
allFinish();
|
||||
}
|
||||
};
|
||||
|
||||
for (var i = 0; i < injectCount; i++) {
|
||||
SVGInjectElement(img[i], options, finish);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// only one img element
|
||||
SVGInjectElement(img, options, allFinish);
|
||||
}
|
||||
};
|
||||
|
||||
// return a Promise object if globally available
|
||||
return typeof Promise == _UNDEFINED_ ? run() : new Promise(run);
|
||||
}
|
||||
|
||||
|
||||
// Injects a single svg element. Options must be already merged with the default options.
|
||||
function SVGInjectElement(imgElem, options, callback) {
|
||||
if (imgElem) {
|
||||
var svgInjectAttributeValue = imgElem[__SVGINJECT];
|
||||
if (!svgInjectAttributeValue) {
|
||||
removeEventListeners(imgElem);
|
||||
|
||||
if (!IS_SVG_SUPPORTED) {
|
||||
svgNotSupported(imgElem, options);
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
// Invoke beforeLoad hook if set. If the beforeLoad returns a value use it as the src for the load
|
||||
// URL path. Else use the imgElem's src attribute value.
|
||||
var beforeLoad = options.beforeLoad;
|
||||
var src = (beforeLoad && beforeLoad(imgElem)) || imgElem[_GET_ATTRIBUTE_]('src');
|
||||
|
||||
if (!src) {
|
||||
// If no image src attribute is set do no injection. This can only be reached by using javascript
|
||||
// because if no src attribute is set the onload and onerror events do not get called
|
||||
if (src === '') {
|
||||
loadFail(imgElem, options);
|
||||
}
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
// set array so later calls can register callbacks
|
||||
var onFinishCallbacks = [];
|
||||
imgElem[__SVGINJECT] = onFinishCallbacks;
|
||||
|
||||
var onFinish = function() {
|
||||
callback();
|
||||
onFinishCallbacks.forEach(function(onFinishCallback) {
|
||||
onFinishCallback();
|
||||
});
|
||||
};
|
||||
|
||||
var absUrl = getAbsoluteUrl(src);
|
||||
var useCacheOption = options.useCache;
|
||||
var makeIdsUniqueOption = options.makeIdsUnique;
|
||||
|
||||
var setSvgLoadCacheValue = function(val) {
|
||||
if (useCacheOption) {
|
||||
svgLoadCache[absUrl].forEach(function(svgLoad) {
|
||||
svgLoad(val);
|
||||
});
|
||||
svgLoadCache[absUrl] = val;
|
||||
}
|
||||
};
|
||||
|
||||
if (useCacheOption) {
|
||||
var svgLoad = svgLoadCache[absUrl];
|
||||
|
||||
var handleLoadValue = function(loadValue) {
|
||||
if (loadValue === LOAD_FAIL) {
|
||||
loadFail(imgElem, options);
|
||||
} else if (loadValue === SVG_INVALID) {
|
||||
svgInvalid(imgElem, options);
|
||||
} else {
|
||||
var hasUniqueIds = loadValue[0];
|
||||
var svgString = loadValue[1];
|
||||
var uniqueIdsSvgString = loadValue[2];
|
||||
var svgElem;
|
||||
|
||||
if (makeIdsUniqueOption) {
|
||||
if (hasUniqueIds === NULL) {
|
||||
// IDs for the SVG string have not been made unique before. This may happen if previous
|
||||
// injection of a cached SVG have been run with the option makedIdsUnique set to false
|
||||
svgElem = buildSvgElement(svgString, false);
|
||||
hasUniqueIds = makeIdsUnique(svgElem, false);
|
||||
|
||||
loadValue[0] = hasUniqueIds;
|
||||
loadValue[2] = hasUniqueIds && svgElemToSvgString(svgElem);
|
||||
} else if (hasUniqueIds) {
|
||||
// Make IDs unique for already cached SVGs with better performance
|
||||
svgString = makeIdsUniqueCached(uniqueIdsSvgString);
|
||||
}
|
||||
}
|
||||
|
||||
svgElem = svgElem || buildSvgElement(svgString, false);
|
||||
|
||||
inject(imgElem, svgElem, absUrl, options);
|
||||
}
|
||||
onFinish();
|
||||
};
|
||||
|
||||
if (typeof svgLoad != _UNDEFINED_) {
|
||||
// Value for url exists in cache
|
||||
if (svgLoad.isCallbackQueue) {
|
||||
// Same url has been cached, but value has not been loaded yet, so add to callbacks
|
||||
svgLoad.push(handleLoadValue);
|
||||
} else {
|
||||
handleLoadValue(svgLoad);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
var svgLoad = [];
|
||||
// set property isCallbackQueue to Array to differentiate from array with cached loaded values
|
||||
svgLoad.isCallbackQueue = true;
|
||||
svgLoadCache[absUrl] = svgLoad;
|
||||
}
|
||||
}
|
||||
|
||||
// Load the SVG because it is not cached or caching is disabled
|
||||
loadSvg(absUrl, function(svgXml, svgString) {
|
||||
// Use the XML from the XHR request if it is an instance of Document. Otherwise
|
||||
// (for example of IE9), create the svg document from the svg string.
|
||||
var svgElem = svgXml instanceof Document ? svgXml.documentElement : buildSvgElement(svgString, true);
|
||||
|
||||
var afterLoad = options.afterLoad;
|
||||
if (afterLoad) {
|
||||
// Invoke afterLoad hook which may modify the SVG element. After load may also return a new
|
||||
// svg element or svg string
|
||||
var svgElemOrSvgString = afterLoad(svgElem, svgString) || svgElem;
|
||||
if (svgElemOrSvgString) {
|
||||
// Update svgElem and svgString because of modifications to the SVG element or SVG string in
|
||||
// the afterLoad hook, so the modified SVG is also used for all later cached injections
|
||||
var isString = typeof svgElemOrSvgString == 'string';
|
||||
svgString = isString ? svgElemOrSvgString : svgElemToSvgString(svgElem);
|
||||
svgElem = isString ? buildSvgElement(svgElemOrSvgString, true) : svgElemOrSvgString;
|
||||
}
|
||||
}
|
||||
|
||||
if (svgElem instanceof SVGElement) {
|
||||
var hasUniqueIds = NULL;
|
||||
if (makeIdsUniqueOption) {
|
||||
hasUniqueIds = makeIdsUnique(svgElem, false);
|
||||
}
|
||||
|
||||
if (useCacheOption) {
|
||||
var uniqueIdsSvgString = hasUniqueIds && svgElemToSvgString(svgElem);
|
||||
// set an array with three entries to the load cache
|
||||
setSvgLoadCacheValue([hasUniqueIds, svgString, uniqueIdsSvgString]);
|
||||
}
|
||||
|
||||
inject(imgElem, svgElem, absUrl, options);
|
||||
} else {
|
||||
svgInvalid(imgElem, options);
|
||||
setSvgLoadCacheValue(SVG_INVALID);
|
||||
}
|
||||
onFinish();
|
||||
}, function() {
|
||||
loadFail(imgElem, options);
|
||||
setSvgLoadCacheValue(LOAD_FAIL);
|
||||
onFinish();
|
||||
});
|
||||
} else {
|
||||
if (Array.isArray(svgInjectAttributeValue)) {
|
||||
// svgInjectAttributeValue is an array. Injection is not complete so register callback
|
||||
svgInjectAttributeValue.push(callback);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
imgNotSet();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the default [options](#options) for SVGInject.
|
||||
*
|
||||
* @param {Object} [options] - default [options](#options) for an injection.
|
||||
*/
|
||||
SVGInject.setOptions = function(options) {
|
||||
defaultOptions = mergeOptions(defaultOptions, options);
|
||||
};
|
||||
|
||||
|
||||
// Create a new instance of SVGInject
|
||||
SVGInject.create = createSVGInject;
|
||||
|
||||
|
||||
/**
|
||||
* Used in onerror Event of an `<img>` element to handle cases when the loading the original src fails
|
||||
* (for example if file is not found or if the browser does not support SVG). This triggers a call to the
|
||||
* options onFail hook if available. The optional second parameter will be set as the new src attribute
|
||||
* for the img element.
|
||||
*
|
||||
* @param {HTMLImageElement} img - an img element
|
||||
* @param {String} [fallbackSrc] - optional parameter fallback src
|
||||
*/
|
||||
SVGInject.err = function(img, fallbackSrc) {
|
||||
if (img) {
|
||||
if (img[__SVGINJECT] != FAIL) {
|
||||
removeEventListeners(img);
|
||||
|
||||
if (!IS_SVG_SUPPORTED) {
|
||||
svgNotSupported(img, defaultOptions);
|
||||
} else {
|
||||
removeOnLoadAttribute(img);
|
||||
loadFail(img, defaultOptions);
|
||||
}
|
||||
if (fallbackSrc) {
|
||||
removeOnLoadAttribute(img);
|
||||
img.src = fallbackSrc;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
imgNotSet();
|
||||
}
|
||||
};
|
||||
|
||||
window[globalName] = SVGInject;
|
||||
|
||||
return SVGInject;
|
||||
}
|
||||
|
||||
var SVGInjectInstance = createSVGInject('SVGInject');
|
||||
|
||||
if (typeof module == 'object' && typeof module.exports == 'object') {
|
||||
module.exports = SVGInjectInstance;
|
||||
}
|
||||
})(window, document);
|
599
public/script.js
599
public/script.js
@ -1,7 +1,7 @@
|
||||
import { humanizedDateTime, favsToHotswap, getMessageTimeStamp, dragElement, isMobile, } from "./scripts/RossAscends-mods.js";
|
||||
import { userStatsHandler, statMesProcess } from './scripts/stats.js';
|
||||
import { encode } from "../scripts/gpt-2-3-tokenizer/mod.js";
|
||||
import { GPT3BrowserTokenizer } from "../scripts/gpt-3-tokenizer/gpt3-tokenizer.js";
|
||||
import { encode } from "../lib/gpt-2-3-tokenizer/mod.js";
|
||||
import { GPT3BrowserTokenizer } from "../lib/gpt-3-tokenizer/gpt3-tokenizer.js";
|
||||
import {
|
||||
generateKoboldWithStreaming,
|
||||
kai_settings,
|
||||
@ -40,7 +40,6 @@ import {
|
||||
generateGroupWrapper,
|
||||
deleteGroup,
|
||||
is_group_generating,
|
||||
printGroups,
|
||||
resetSelectedGroup,
|
||||
select_group_chats,
|
||||
regenerateGroup,
|
||||
@ -55,13 +54,13 @@ import {
|
||||
deleteGroupChat,
|
||||
renameGroupChat,
|
||||
importGroupChat,
|
||||
getGroupBlock,
|
||||
} from "./scripts/group-chats.js";
|
||||
|
||||
import {
|
||||
collapseNewlines,
|
||||
loadPowerUserSettings,
|
||||
playMessageSound,
|
||||
sortCharactersList,
|
||||
fixMarkdown,
|
||||
power_user,
|
||||
pygmalion_options,
|
||||
@ -72,10 +71,9 @@ import {
|
||||
persona_description_positions,
|
||||
loadMovingUIState,
|
||||
getCustomStoppingStrings,
|
||||
fuzzySearchCharacters,
|
||||
MAX_CONTEXT_DEFAULT,
|
||||
fuzzySearchGroups,
|
||||
renderStoryString,
|
||||
sortEntitiesList,
|
||||
} from "./scripts/power-user.js";
|
||||
|
||||
import {
|
||||
@ -159,12 +157,13 @@ import {
|
||||
secret_state,
|
||||
writeSecret
|
||||
} from "./scripts/secrets.js";
|
||||
import { EventEmitter } from './scripts/eventemitter.js';
|
||||
import { EventEmitter } from './lib/eventemitter.js';
|
||||
import { markdownExclusionExt } from "./scripts/showdown-exclusion.js";
|
||||
import { NOTE_MODULE_NAME, metadata_keys, setFloatingPrompt, shouldWIAddPrompt } from "./scripts/authors-note.js";
|
||||
import { deviceInfo } from "./scripts/RossAscends-mods.js";
|
||||
import { registerPromptManagerMigration } from "./scripts/PromptManager.js";
|
||||
import { getRegexedString, regex_placement } from "./scripts/extensions/regex/engine.js";
|
||||
import { FILTER_TYPES, FilterHelper } from "./scripts/filters.js";
|
||||
|
||||
//exporting functions and vars for mods
|
||||
export {
|
||||
@ -233,7 +232,6 @@ export {
|
||||
talkativeness_default,
|
||||
default_ch_mes,
|
||||
extension_prompt_types,
|
||||
updateVisibleDivs,
|
||||
mesForShowdownParse,
|
||||
printCharacters,
|
||||
}
|
||||
@ -753,7 +751,7 @@ let create_save = {
|
||||
};
|
||||
|
||||
//animation right menu
|
||||
let animation_duration = 250;
|
||||
let animation_duration = 125;
|
||||
let animation_easing = "ease-in-out";
|
||||
let popup_type = "";
|
||||
let bg_file_for_del = "";
|
||||
@ -817,8 +815,9 @@ let token;
|
||||
|
||||
var PromptArrayItemForRawPromptDisplay;
|
||||
|
||||
export let active_character = ""
|
||||
export let active_group = ""
|
||||
export let active_character = "";
|
||||
export let active_group = "";
|
||||
export const entitiesFilter = new FilterHelper(debounce(printCharacters, 100));
|
||||
|
||||
export function getRequestHeaders() {
|
||||
return {
|
||||
@ -965,60 +964,153 @@ function resultCheckStatus() {
|
||||
$("#api_button_textgenerationwebui").css("display", "inline-block");
|
||||
}
|
||||
|
||||
async function printCharacters() {
|
||||
$("#rm_print_characters_block").empty();
|
||||
characters.forEach(function (item, i, arr) {
|
||||
let this_avatar = default_avatar;
|
||||
if (item.avatar != "none") {
|
||||
this_avatar = getThumbnailUrl('avatar', item.avatar);
|
||||
}
|
||||
// Populate the template
|
||||
const template = $('#character_template .character_select').clone();
|
||||
template.attr({ 'chid': i, 'id': `CharID${i}` });
|
||||
template.find('img').attr('src', this_avatar);
|
||||
template.find('.avatar').attr('title', item.avatar);
|
||||
template.find('.ch_name').text(item.name);
|
||||
if (power_user.show_card_avatar_urls) {
|
||||
template.find('.ch_avatar_url').text(item.avatar);
|
||||
}
|
||||
template.find('.ch_fav_icon').css("display", 'none');
|
||||
template.toggleClass('is_fav', item.fav || item.fav == 'true');
|
||||
template.find('.ch_fav').val(item.fav);
|
||||
export function selectCharacterById(id) {
|
||||
if (characters[id] == undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const description = item.data?.creator_notes?.split('\n', 1)[0] || '';
|
||||
if (description) {
|
||||
template.find('.ch_description').text(description);
|
||||
}
|
||||
else {
|
||||
template.find('.ch_description').hide();
|
||||
}
|
||||
if (selected_group && is_group_generating) {
|
||||
return;
|
||||
}
|
||||
|
||||
const version = item.data?.character_version || '';
|
||||
if (version) {
|
||||
template.find('.character_version').text(version);
|
||||
if (selected_group || this_chid !== id) {
|
||||
//if clicked on a different character from what was currently selected
|
||||
if (!is_send_press) {
|
||||
cancelTtsPlay();
|
||||
resetSelectedGroup();
|
||||
this_edit_mes_id = undefined;
|
||||
selected_button = "character_edit";
|
||||
this_chid = id;
|
||||
clearChat();
|
||||
chat.length = 0;
|
||||
chat_metadata = {};
|
||||
getChat();
|
||||
}
|
||||
else {
|
||||
template.find('.character_version').hide();
|
||||
} else {
|
||||
//if clicked on character that was already selected
|
||||
selected_button = "character_edit";
|
||||
select_selected_character(this_chid);
|
||||
}
|
||||
}
|
||||
|
||||
function getCharacterBlock(item, id) {
|
||||
let this_avatar = default_avatar;
|
||||
if (item.avatar != "none") {
|
||||
this_avatar = getThumbnailUrl('avatar', item.avatar);
|
||||
}
|
||||
// Populate the template
|
||||
const template = $('#character_template .character_select').clone();
|
||||
template.attr({ 'chid': id, 'id': `CharID${id}` });
|
||||
template.find('img').attr('src', this_avatar);
|
||||
template.find('.avatar').attr('title', item.avatar);
|
||||
template.find('.ch_name').text(item.name);
|
||||
if (power_user.show_card_avatar_urls) {
|
||||
template.find('.ch_avatar_url').text(item.avatar);
|
||||
}
|
||||
template.find('.ch_fav_icon').css("display", 'none');
|
||||
template.toggleClass('is_fav', item.fav || item.fav == 'true');
|
||||
template.find('.ch_fav').val(item.fav);
|
||||
|
||||
const description = item.data?.creator_notes?.split('\n', 1)[0] || '';
|
||||
if (description) {
|
||||
template.find('.ch_description').text(description);
|
||||
}
|
||||
else {
|
||||
template.find('.ch_description').hide();
|
||||
}
|
||||
|
||||
const version = item.data?.character_version || '';
|
||||
if (version) {
|
||||
template.find('.character_version').text(version);
|
||||
}
|
||||
else {
|
||||
template.find('.character_version').hide();
|
||||
}
|
||||
|
||||
// Display inline tags
|
||||
const tags = getTagsList(item.avatar);
|
||||
const tagsElement = template.find('.tags');
|
||||
tags.forEach(tag => appendTagToList(tagsElement, tag, {}));
|
||||
|
||||
// Add to the list
|
||||
return template;
|
||||
}
|
||||
|
||||
async function printCharacters(fullRefresh = false) {
|
||||
const storageKey = 'Characters_PerPage';
|
||||
$("#rm_print_characters_pagination").pagination({
|
||||
dataSource: getEntitiesList({ doFilter: true }),
|
||||
pageSize: Number(localStorage.getItem(storageKey)) || 50,
|
||||
sizeChangerOptions: [10, 25, 50, 100, 250, 500, 1000],
|
||||
pageRange: 1,
|
||||
position: 'top',
|
||||
showPageNumbers: false,
|
||||
showSizeChanger: true,
|
||||
prevText: '<',
|
||||
nextText: '>',
|
||||
showNavigator: true,
|
||||
callback: function (data) {
|
||||
$("#rm_print_characters_block").empty();
|
||||
for (const i of data) {
|
||||
if (i.type === 'character') {
|
||||
$("#rm_print_characters_block").append(getCharacterBlock(i.item, i.id));
|
||||
}
|
||||
if (i.type === 'group') {
|
||||
$("#rm_print_characters_block").append(getGroupBlock(i.item));
|
||||
}
|
||||
}
|
||||
},
|
||||
afterSizeSelectorChange: function (e) {
|
||||
localStorage.setItem(storageKey, e.target.value);
|
||||
}
|
||||
|
||||
// Display inline tags
|
||||
const tags = getTagsList(item.avatar);
|
||||
const tagsElement = template.find('.tags');
|
||||
tags.forEach(tag => appendTagToList(tagsElement, tag, {}));
|
||||
|
||||
// Add to the list
|
||||
$("#rm_print_characters_block").append(template);
|
||||
});
|
||||
|
||||
printTagFilters(tag_filter_types.character);
|
||||
printTagFilters(tag_filter_types.group_member);
|
||||
printGroups();
|
||||
sortCharactersList();
|
||||
favsToHotswap();
|
||||
await delay(300);
|
||||
updateVisibleDivs('#rm_print_characters_block', true);
|
||||
displayOverrideWarnings();
|
||||
|
||||
if (fullRefresh) {
|
||||
printTagFilters(tag_filter_types.character);
|
||||
printTagFilters(tag_filter_types.group_member);
|
||||
|
||||
await delay(300);
|
||||
displayOverrideWarnings();
|
||||
}
|
||||
}
|
||||
|
||||
export function getEntitiesList({ doFilter } = {}) {
|
||||
let entities = [];
|
||||
entities.push(...characters.map((item, index) => ({ item, id: index, type: 'character' })));
|
||||
entities.push(...groups.map((item) => ({ item, id: item.id, type: 'group' })));
|
||||
|
||||
if (doFilter) {
|
||||
entities = entitiesFilter.applyFilters(entities);
|
||||
}
|
||||
|
||||
sortEntitiesList(entities);
|
||||
return entities;
|
||||
}
|
||||
|
||||
async function getOneCharacter(avatarUrl) {
|
||||
const response = await fetch("/getonecharacter", {
|
||||
method: "POST",
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
avatar_url: avatarUrl,
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const getData = await response.json();
|
||||
getData['name'] = DOMPurify.sanitize(getData['name']);
|
||||
getData['chat'] = String(getData['chat']);
|
||||
|
||||
const indexOf = characters.findIndex(x => x.avatar === avatarUrl);
|
||||
|
||||
if (indexOf !== -1) {
|
||||
characters[indexOf] = getData;
|
||||
} else {
|
||||
toastr.error(`Character ${avatarUrl} not found in the list`, "Error", { timeOut: 5000, preventDuplicates: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getCharacters() {
|
||||
@ -1050,10 +1142,7 @@ async function getCharacters() {
|
||||
}
|
||||
|
||||
await getGroups();
|
||||
await printCharacters();
|
||||
|
||||
|
||||
updateCharacterCount('#rm_print_characters_block > div');
|
||||
await printCharacters(true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1357,25 +1446,25 @@ function insertSVGIcon(mes, extra) {
|
||||
modelName = extra.api;
|
||||
}
|
||||
|
||||
// Fetch the SVG based on the modelName
|
||||
$.get(`/img/${modelName}.svg`, function (data) {
|
||||
// Extract the SVG content from the XML data
|
||||
let svg = $(data).find('svg');
|
||||
|
||||
// Add classes for styling and identification
|
||||
svg.addClass('icon-svg timestamp-icon');
|
||||
const image = new Image();
|
||||
// Add classes for styling and identification
|
||||
image.classList.add('icon-svg', 'timestamp-icon');
|
||||
image.src = `/img/${modelName}.svg`;
|
||||
|
||||
image.onload = async function () {
|
||||
// Check if an SVG already exists adjacent to the timestamp
|
||||
let existingSVG = mes.find('.timestamp').next('.timestamp-icon');
|
||||
|
||||
if (existingSVG.length) {
|
||||
// Replace existing SVG
|
||||
existingSVG.replaceWith(svg);
|
||||
existingSVG.replaceWith(image);
|
||||
} else {
|
||||
// Append the new SVG if none exists
|
||||
mes.find('.timestamp').after(svg);
|
||||
mes.find('.timestamp').after(image);
|
||||
}
|
||||
});
|
||||
|
||||
await SVGInject(this);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1680,7 +1769,6 @@ function scrollChatToBottom() {
|
||||
function substituteParams(content, _name1, _name2, _original, _group) {
|
||||
_name1 = _name1 ?? name1;
|
||||
_name2 = _name2 ?? name2;
|
||||
_original = _original || '';
|
||||
_group = _group ?? name2;
|
||||
|
||||
if (!content) {
|
||||
@ -1832,8 +1920,13 @@ function getStoppingStrings(isImpersonate, addSpace) {
|
||||
}
|
||||
|
||||
if (power_user.instruct.enabled) {
|
||||
addInstructSequence(power_user.instruct.input_sequence);
|
||||
addInstructSequence(power_user.instruct.output_sequence);
|
||||
const input_sequence = power_user.instruct.input_sequence;
|
||||
const output_sequence = power_user.instruct.output_sequence;
|
||||
const last_output_sequence = power_user.instruct.last_output_sequence;
|
||||
|
||||
const combined_sequence = `${input_sequence}\n${output_sequence}\n${last_output_sequence}`;
|
||||
|
||||
combined_sequence.split('\n').filter((line, index, self) => self.indexOf(line) === index).forEach(addInstructSequence);
|
||||
}
|
||||
|
||||
if (power_user.custom_stopping_strings) {
|
||||
@ -2032,12 +2125,14 @@ function baseChatReplace(value, name1, name2) {
|
||||
if (power_user.collapse_newlines) {
|
||||
value = collapseNewlines(value);
|
||||
}
|
||||
|
||||
value = value.replace(/\r/g, '');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function isStreamingEnabled() {
|
||||
return ((main_api == 'openai' && oai_settings.stream_openai && oai_settings.chat_completion_source !== chat_completion_sources.SCALE)
|
||||
return ((main_api == 'openai' && oai_settings.stream_openai && oai_settings.chat_completion_source !== chat_completion_sources.SCALE && oai_settings.chat_completion_source !== chat_completion_sources.AI21)
|
||||
|| (main_api == 'kobold' && kai_settings.streaming_kobold && kai_settings.can_use_streaming)
|
||||
|| (main_api == 'novel' && nai_settings.streaming_novel)
|
||||
|| (main_api == 'textgenerationwebui' && textgenerationwebui_settings.streaming))
|
||||
@ -2364,11 +2459,14 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
|
||||
const memberIds = enabledMembers
|
||||
.map((member) => characterIndexMap.get(member))
|
||||
.filter((index) => index !== undefined);
|
||||
.filter((index) => index !== undefined && index !== null);
|
||||
|
||||
if (memberIds.length > 0) {
|
||||
setCharacterId(memberIds[0]);
|
||||
setCharacterName('');
|
||||
} else {
|
||||
console.log('No enabled members found');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2431,8 +2529,8 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
let charPersonality = baseChatReplace(characters[this_chid].personality.trim(), name1, name2);
|
||||
let Scenario = baseChatReplace(scenarioText.trim(), name1, name2);
|
||||
let mesExamples = baseChatReplace(characters[this_chid].mes_example.trim(), name1, name2);
|
||||
let systemPrompt = baseChatReplace(characters[this_chid].data?.system_prompt?.trim(), name1, name2);
|
||||
let jailbreakPrompt = baseChatReplace(characters[this_chid].data?.post_history_instructions?.trim(), name1, name2);
|
||||
let systemPrompt = power_user.prefer_character_prompt ? baseChatReplace(characters[this_chid].data?.system_prompt?.trim(), name1, name2) : '';
|
||||
let jailbreakPrompt = power_user.prefer_character_jailbreak ? baseChatReplace(characters[this_chid].data?.post_history_instructions?.trim(), name1, name2) : '';
|
||||
|
||||
// Parse example messages
|
||||
if (!mesExamples.startsWith('<START>')) {
|
||||
@ -2885,8 +2983,9 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
bias: promptBias,
|
||||
type: type,
|
||||
quietPrompt: quiet_prompt,
|
||||
jailbreakPrompt: jailbreakPrompt,
|
||||
cyclePrompt: cyclePrompt,
|
||||
systemPromptOverride: systemPrompt,
|
||||
jailbreakPromptOverride: jailbreakPrompt,
|
||||
}, dryRun);
|
||||
generate_data = { prompt: prompt };
|
||||
|
||||
@ -3921,10 +4020,28 @@ function cleanUpMessage(getMessage, isImpersonate, isContinue, displayIncomplete
|
||||
}
|
||||
}
|
||||
if (isInstruct && power_user.instruct.input_sequence && isImpersonate) {
|
||||
getMessage = getMessage.replaceAll(power_user.instruct.input_sequence, '');
|
||||
//getMessage = getMessage.replaceAll(power_user.instruct.input_sequence, '');
|
||||
power_user.instruct.input_sequence.split('\n')
|
||||
.filter(line => line.trim() !== '')
|
||||
.forEach(line => {
|
||||
getMessage = getMessage.replaceAll(line, '');
|
||||
});
|
||||
}
|
||||
if (isInstruct && power_user.instruct.output_sequence && !isImpersonate) {
|
||||
getMessage = getMessage.replaceAll(power_user.instruct.output_sequence, '');
|
||||
//getMessage = getMessage.replaceAll(power_user.instruct.output_sequence, '');
|
||||
power_user.instruct.output_sequence.split('\n')
|
||||
.filter(line => line.trim() !== '')
|
||||
.forEach(line => {
|
||||
getMessage = getMessage.replaceAll(line, '');
|
||||
});
|
||||
}
|
||||
if (isInstruct && power_user.instruct.last_output_sequence && !isImpersonate) {
|
||||
//getMessage = getMessage.replaceAll(power_user.instruct.last_output_sequence, '');
|
||||
power_user.instruct.last_output_sequence.split('\n')
|
||||
.filter(line => line.trim() !== '')
|
||||
.forEach(line => {
|
||||
getMessage = getMessage.replaceAll(line, '');
|
||||
});
|
||||
}
|
||||
// clean-up group message from excessive generations
|
||||
if (selected_group) {
|
||||
@ -4209,7 +4326,7 @@ async function renameCharacter() {
|
||||
if (newChId !== -1) {
|
||||
// Select the character after the renaming
|
||||
this_chid = -1;
|
||||
$(`.character_select[chid="${newChId}"]`).click();
|
||||
selectCharacterById(String(newChId));
|
||||
|
||||
// Async delay to update UI
|
||||
await delay(1);
|
||||
@ -4303,7 +4420,6 @@ async function saveChat(chat_name, withMetadata, mesId) {
|
||||
const metadata = { ...chat_metadata, ...(withMetadata || {}) };
|
||||
let file_name = chat_name ?? characters[this_chid].chat;
|
||||
characters[this_chid]['date_last_chat'] = Date.now();
|
||||
sortCharactersList();
|
||||
chat.forEach(function (item, i) {
|
||||
if (item["is_group"]) {
|
||||
toastr.error('Trying to save group chat with regular saveChat function. Aborting to prevent corruption.');
|
||||
@ -4451,10 +4567,9 @@ async function getChat() {
|
||||
chat_create_date = humanizedDateTime();
|
||||
}
|
||||
await getChatResult();
|
||||
await saveChat();
|
||||
saveChatDebounced();
|
||||
eventSource.emit('chatLoaded', { detail: { id: this_chid, character: characters[this_chid] } });
|
||||
|
||||
|
||||
setTimeout(function () {
|
||||
$('#send_textarea').click();
|
||||
$('#send_textarea').focus();
|
||||
@ -4506,7 +4621,7 @@ async function openCharacterChat(file_name) {
|
||||
chat_metadata = {};
|
||||
await getChat();
|
||||
$("#selected_chat_pole").val(file_name);
|
||||
$("#create_button").click();
|
||||
await createOrEditCharacter();
|
||||
}
|
||||
|
||||
////////// OPTIMZED MAIN API CHANGE FUNCTION ////////////
|
||||
@ -4625,6 +4740,7 @@ function changeMainAPI() {
|
||||
case chat_completion_sources.WINDOWAI:
|
||||
case chat_completion_sources.CLAUDE:
|
||||
case chat_completion_sources.OPENAI:
|
||||
case chat_completion_sources.AI21:
|
||||
default:
|
||||
setupChatCompletionPromptManager(oai_settings);
|
||||
break;
|
||||
@ -5510,6 +5626,66 @@ async function messageEditDone(div) {
|
||||
await saveChatConditional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the chat content for each chat file from the server and compiles them into a dictionary.
|
||||
* The function iterates over a provided list of chat metadata and requests the actual chat content
|
||||
* for each chat, either as an individual chat or a group chat based on the context.
|
||||
*
|
||||
* @param {Array} data - An array containing metadata about each chat such as file_name.
|
||||
* @param {boolean} isGroupChat - A flag indicating if the chat is a group chat.
|
||||
* @returns {Object} chat_dict - A dictionary where each key is a file_name and the value is the
|
||||
* corresponding chat content fetched from the server.
|
||||
*/
|
||||
export async function getChatsFromFiles(data, isGroupChat) {
|
||||
const context = getContext();
|
||||
let chat_dict = {};
|
||||
let chat_list = Object.values(data).sort((a, b) => a["file_name"].localeCompare(b["file_name"])).reverse();
|
||||
|
||||
for (const { file_name } of chat_list) {
|
||||
try {
|
||||
const endpoint = isGroupChat ? '/getgroupchat' : '/getchat';
|
||||
const requestBody = isGroupChat
|
||||
? JSON.stringify({ id: file_name })
|
||||
: JSON.stringify({
|
||||
ch_name: characters[context.characterId].name,
|
||||
file_name: file_name.replace('.jsonl', ''),
|
||||
avatar_url: characters[context.characterId].avatar
|
||||
});
|
||||
|
||||
const chatResponse = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: requestBody,
|
||||
cache: 'no-cache',
|
||||
});
|
||||
|
||||
if (!chatResponse.ok) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const currentChat = await chatResponse.json();
|
||||
if (!isGroupChat) {
|
||||
// remove the first message, which is metadata, only for individual chats
|
||||
currentChat.shift();
|
||||
}
|
||||
chat_dict[file_name] = currentChat;
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
return chat_dict;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the metadata of all past chats related to a specific character based on its avatar URL.
|
||||
* The function sends a POST request to the server to retrieve all chats for the character. It then
|
||||
* processes the received data, sorts it by the file name, and returns the sorted data.
|
||||
*
|
||||
* @returns {Array} - An array containing metadata of all past chats of the character, sorted
|
||||
* in descending order by file name. Returns `undefined` if the fetch request is unsuccessful.
|
||||
*/
|
||||
async function getPastCharacterChats() {
|
||||
const response = await fetch("/getallchatsofcharacter", {
|
||||
method: 'POST',
|
||||
@ -5527,6 +5703,12 @@ async function getPastCharacterChats() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the past chats for a character or a group based on the selected context.
|
||||
* The function first fetches the chats, processes them, and then displays them in
|
||||
* the HTML. It also has a built-in search functionality that allows filtering the
|
||||
* displayed chats based on a search query.
|
||||
*/
|
||||
export async function displayPastChats() {
|
||||
$("#select_chat_div").empty();
|
||||
|
||||
@ -5535,45 +5717,70 @@ export async function displayPastChats() {
|
||||
const currentChat = selected_group ? group?.chat_id : characters[this_chid]["chat"];
|
||||
const displayName = selected_group ? group?.name : characters[this_chid].name;
|
||||
const avatarImg = selected_group ? group?.avatar_url : getThumbnailUrl('avatar', characters[this_chid]['avatar']);
|
||||
|
||||
const rawChats = await getChatsFromFiles(data, selected_group);
|
||||
// Sort by last message date descending
|
||||
data.sort((a, b) => sortMoments(timestampToMoment(a.last_mes), timestampToMoment(b.last_mes)));
|
||||
|
||||
console.log(data);
|
||||
$("#load_select_chat_div").css("display", "none");
|
||||
$("#ChatHistoryCharName").text(displayName);
|
||||
for (const key in data) {
|
||||
let strlen = 300;
|
||||
let mes = data[key]["mes"];
|
||||
|
||||
if (mes !== undefined) {
|
||||
if (mes.length > strlen) {
|
||||
mes = "..." + mes.substring(mes.length - strlen);
|
||||
}
|
||||
const chat_items = data[key]["chat_items"];
|
||||
const file_size = data[key]["file_size"];
|
||||
const fileName = data[key]['file_name'];
|
||||
const timestamp = timestampToMoment(data[key]['last_mes']).format('LL LT');
|
||||
const template = $('#past_chat_template .select_chat_block_wrapper').clone();
|
||||
template.find('.select_chat_block').attr('file_name', fileName);
|
||||
template.find('.avatar img').attr('src', avatarImg);
|
||||
template.find('.select_chat_block_filename').text(fileName);
|
||||
template.find('.chat_file_size').text(" (" + file_size + ")");
|
||||
template.find('.chat_messages_num').text(" (" + chat_items + " messages)");
|
||||
template.find('.select_chat_block_mes').text(mes);
|
||||
template.find('.PastChat_cross').attr('file_name', fileName);
|
||||
template.find('.chat_messages_date').text(timestamp);
|
||||
const displayChats = (searchQuery) => {
|
||||
$("#select_chat_div").empty(); // Clear the current chats before appending filtered chats
|
||||
|
||||
if (selected_group) {
|
||||
template.find('.avatar img').replaceWith(getGroupAvatar(group));
|
||||
}
|
||||
const filteredData = data.filter(chat => {
|
||||
const fileName = chat['file_name'];
|
||||
const chatContent = rawChats[fileName];
|
||||
|
||||
$("#select_chat_div").append(template);
|
||||
return chatContent && Object.values(chatContent).some(message => message.mes.toLowerCase().includes(searchQuery.toLowerCase()));
|
||||
});
|
||||
|
||||
if (currentChat === fileName.toString().replace(".jsonl", "")) {
|
||||
$("#select_chat_div").find(".select_chat_block:last").attr("highlight", true);
|
||||
console.log(filteredData);
|
||||
for (const key in filteredData) {
|
||||
let strlen = 300;
|
||||
let mes = filteredData[key]["mes"];
|
||||
|
||||
if (mes !== undefined) {
|
||||
if (mes.length > strlen) {
|
||||
mes = "..." + mes.substring(mes.length - strlen);
|
||||
}
|
||||
const chat_items = data[key]["chat_items"];
|
||||
const file_size = data[key]["file_size"];
|
||||
const fileName = data[key]['file_name'];
|
||||
const timestamp = timestampToMoment(data[key]['last_mes']).format('LL LT');
|
||||
const template = $('#past_chat_template .select_chat_block_wrapper').clone();
|
||||
template.find('.select_chat_block').attr('file_name', fileName);
|
||||
template.find('.avatar img').attr('src', avatarImg);
|
||||
template.find('.select_chat_block_filename').text(fileName);
|
||||
template.find('.chat_file_size').text(" (" + file_size + ")");
|
||||
template.find('.chat_messages_num').text(" (" + chat_items + " messages)");
|
||||
template.find('.select_chat_block_mes').text(mes);
|
||||
template.find('.PastChat_cross').attr('file_name', fileName);
|
||||
template.find('.chat_messages_date').text(timestamp);
|
||||
|
||||
if (selected_group) {
|
||||
template.find('.avatar img').replaceWith(getGroupAvatar(group));
|
||||
}
|
||||
|
||||
$("#select_chat_div").append(template);
|
||||
|
||||
if (currentChat === fileName.toString().replace(".jsonl", "")) {
|
||||
$("#select_chat_div").find(".select_chat_block:last").attr("highlight", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
displayChats(''); // Display all by default
|
||||
|
||||
const debouncedDisplay = debounce((searchQuery) => {
|
||||
displayChats(searchQuery);
|
||||
}, 300);
|
||||
|
||||
// Define the search input listener
|
||||
$("#select_chat_search").on("input", function () {
|
||||
const searchQuery = $(this).val();
|
||||
debouncedDisplay(searchQuery);
|
||||
});
|
||||
}
|
||||
|
||||
//************************************************************
|
||||
@ -5616,11 +5823,11 @@ async function getStatusNovel() {
|
||||
|
||||
function selectRightMenuWithAnimation(selectedMenuId) {
|
||||
const displayModes = {
|
||||
'rm_info_block': 'flex',
|
||||
'rm_group_chats_block': 'flex',
|
||||
'rm_api_block': 'grid',
|
||||
'rm_characters_block': 'flex',
|
||||
};
|
||||
$('#hideCharPanelAvatarButton').toggle(selectedMenuId === 'rm_ch_create_block');
|
||||
document.querySelectorAll('#right-nav-panel .right_menu').forEach((menu) => {
|
||||
$(menu).css('display', 'none');
|
||||
|
||||
@ -5674,7 +5881,6 @@ function select_rm_info(type, charId, previousCharId = null) {
|
||||
toastr.success(`Character Imported: ${displayName}`);
|
||||
}
|
||||
|
||||
getCharacters();
|
||||
selectRightMenuWithAnimation('rm_characters_block');
|
||||
|
||||
setTimeout(function () {
|
||||
@ -5852,7 +6058,7 @@ function select_rm_characters() {
|
||||
menu_type = "characters";
|
||||
selectRightMenuWithAnimation('rm_characters_block');
|
||||
setRightTabSelectedClass('rm_button_characters');
|
||||
updateVisibleDivs('#rm_print_characters_block', true);
|
||||
printCharacters(false); // Do a quick refresh of the characters list
|
||||
}
|
||||
|
||||
function setExtensionPrompt(key, value, position, depth) {
|
||||
@ -6082,10 +6288,10 @@ function hideSwipeButtons() {
|
||||
|
||||
async function saveMetadata() {
|
||||
if (selected_group) {
|
||||
await editGroup(selected_group, true, false);
|
||||
await editGroup(selected_group, false, false);
|
||||
}
|
||||
else {
|
||||
await saveChat();
|
||||
saveChatDebounced();
|
||||
}
|
||||
}
|
||||
|
||||
@ -6199,7 +6405,6 @@ async function deleteMessageImage() {
|
||||
mesBlock.find('.mes_img_container').removeClass('img_extra');
|
||||
mesBlock.find('.mes_img').attr('src', '');
|
||||
saveChatConditional();
|
||||
/*updateVisibleDivs('#chat', false);*/
|
||||
}
|
||||
|
||||
function enlargeMessageImage() {
|
||||
@ -6490,12 +6695,8 @@ async function createOrEditCharacter(e) {
|
||||
createTagMapFromList("#tagList", html);
|
||||
await getCharacters();
|
||||
|
||||
$("#rm_info_block").transition({ opacity: 0, duration: 0 });
|
||||
var $prev_img = $("#avatar_div_div").clone();
|
||||
$("#rm_info_avatar").append($prev_img);
|
||||
select_rm_info(`char_create`, html, oldSelectedChar);
|
||||
|
||||
$("#rm_info_block").transition({ opacity: 1.0, duration: 2000 });
|
||||
crop_data = undefined;
|
||||
},
|
||||
error: function (jqXHR, exception) {
|
||||
@ -6584,7 +6785,8 @@ async function createOrEditCharacter(e) {
|
||||
}
|
||||
}
|
||||
$("#create_button").removeAttr("disabled");
|
||||
await getCharacters();
|
||||
|
||||
await getOneCharacter(formData.get('avatar_url'));
|
||||
|
||||
$("#add_avatar_button").replaceWith(
|
||||
$("#add_avatar_button").val("").clone(true)
|
||||
@ -6932,51 +7134,6 @@ const swipe_right = () => {
|
||||
}
|
||||
}
|
||||
|
||||
export function updateCharacterCount(characterSelector) {
|
||||
const visibleCharacters = $(characterSelector)
|
||||
.not(".hiddenBySearch")
|
||||
.not(".hiddenByTag")
|
||||
.not(".hiddenByGroup")
|
||||
.not(".hiddenByGroupMember")
|
||||
.not(".hiddenByFav");
|
||||
const visibleCharacterCount = visibleCharacters.length;
|
||||
const totalCharacterCount = $(characterSelector).length;
|
||||
|
||||
$("#rm_character_count").text(
|
||||
`(${visibleCharacterCount} / ${totalCharacterCount}) Characters`
|
||||
);
|
||||
console.log("visibleCharacters.length: " + visibleCharacters.length);
|
||||
}
|
||||
|
||||
function updateVisibleDivs(containerSelector, resizecontainer) {
|
||||
var $container = $(containerSelector);
|
||||
var $children = $container.children();
|
||||
var totalHeight = 0;
|
||||
$children.each(function () {
|
||||
totalHeight += $(this).outerHeight();
|
||||
});
|
||||
if (resizecontainer) {
|
||||
$container.css({
|
||||
height: totalHeight,
|
||||
});
|
||||
}
|
||||
var containerTop = $container.offset() ? $container.offset().top : 0;
|
||||
var firstVisibleIndex = null;
|
||||
var lastVisibleIndex = null;
|
||||
$children.each(function (index) {
|
||||
var $child = $(this);
|
||||
var childTop = $child.offset().top - containerTop;
|
||||
var childBottom = childTop + $child.outerHeight();
|
||||
if (childTop <= $container.height() && childBottom >= 0) {
|
||||
if (firstVisibleIndex === null) {
|
||||
firstVisibleIndex = index;
|
||||
}
|
||||
lastVisibleIndex = index;
|
||||
}
|
||||
$child.toggleClass('hiddenByScroll', childTop > $container.height() || childBottom < 0);
|
||||
});
|
||||
}
|
||||
|
||||
function displayOverrideWarnings() {
|
||||
if (!this_chid || !selected_group) {
|
||||
$('.prompt_overridden').hide();
|
||||
@ -7024,6 +7181,11 @@ function connectAPISlash(_, text) {
|
||||
source: 'openrouter',
|
||||
button: '#api_button_openai',
|
||||
},
|
||||
'ai21': {
|
||||
selected: 'openai',
|
||||
source: 'ai21',
|
||||
button: '#api_button_openai',
|
||||
}
|
||||
};
|
||||
|
||||
const apiConfig = apiMap[text];
|
||||
@ -7086,12 +7248,6 @@ function importCharacter(file) {
|
||||
|
||||
if (data.file_name !== undefined) {
|
||||
$('#character_search_bar').val('').trigger('input');
|
||||
$("#rm_info_block").transition({ opacity: 0, duration: 0 });
|
||||
var $prev_img = $("#avatar_div_div").clone();
|
||||
$prev_img
|
||||
.children("img")
|
||||
.attr("src", "characters/" + data.file_name + ".png");
|
||||
$("#rm_info_avatar").append($prev_img);
|
||||
|
||||
let oldSelectedChar = null;
|
||||
if (this_chid != undefined && this_chid != "invalid-safety-id") {
|
||||
@ -7106,7 +7262,6 @@ function importCharacter(file) {
|
||||
let importedCharacter = currentContext.characters.find(character => character.avatar === avatarFileName);
|
||||
await importTags(importedCharacter);
|
||||
}
|
||||
$("#rm_info_block").transition({ opacity: 1, duration: 1000 });
|
||||
}
|
||||
},
|
||||
error: function (jqXHR, exception) {
|
||||
@ -7161,8 +7316,7 @@ function doCharListDisplaySwitch() {
|
||||
console.debug('toggling body charListGrid state')
|
||||
$("body").toggleClass('charListGrid')
|
||||
power_user.charListGrid = $("body").hasClass("charListGrid") ? true : false;
|
||||
saveSettingsDebounced()
|
||||
updateVisibleDivs('#rm_print_characters_block', true);
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function doCloseChat() {
|
||||
@ -7250,24 +7404,17 @@ $(document).ready(function () {
|
||||
}
|
||||
|
||||
registerSlashCommand('dupe', DupeChar, [], "– duplicates the currently selected character", true, true);
|
||||
registerSlashCommand('api', connectAPISlash, [], "(kobold, horde, novel, ooba, oai, claude, windowai) – connect to an API", true, true);
|
||||
registerSlashCommand('api', connectAPISlash, [], "(kobold, horde, novel, ooba, oai, claude, windowai, ai21) – connect to an API", true, true);
|
||||
registerSlashCommand('impersonate', doImpersonate, ['imp'], "- calls an impersonation response", true, true);
|
||||
registerSlashCommand('delchat', doDeleteChat, [], "- deletes the current chat", true, true);
|
||||
registerSlashCommand('closechat', doCloseChat, [], "- closes the current chat", true, true);
|
||||
registerSlashCommand('panels', doTogglePanels, ['togglepanels'], "- toggle UI panels on/off", true, true);
|
||||
|
||||
|
||||
|
||||
setTimeout(function () {
|
||||
$("#groupControlsToggle").trigger('click');
|
||||
$("#groupCurrentMemberListToggle .inline-drawer-icon").trigger('click');
|
||||
}, 200);
|
||||
|
||||
|
||||
$("#rm_print_characters_block").on('scroll', debounce(() => {
|
||||
updateVisibleDivs('#rm_print_characters_block', true);
|
||||
}, 5));
|
||||
|
||||
$('#chat').on('scroll', async () => {
|
||||
// if on the start of the chat and has hidden messages
|
||||
if ($('#chat').scrollTop() === 0 && $('#chat').children('.mes').not(':visible').length > 0) {
|
||||
@ -7330,43 +7477,8 @@ $(document).ready(function () {
|
||||
$(document).on('click', '.swipe_left', swipe_left);
|
||||
|
||||
$("#character_search_bar").on("input", function () {
|
||||
const selector = ['#rm_print_characters_block .character_select', '#rm_print_characters_block .group_select'].join(',');
|
||||
const searchValue = $(this).val().trim().toLowerCase();
|
||||
const fuzzySearchCharactersResults = power_user.fuzzy_search ? fuzzySearchCharacters(searchValue) : [];
|
||||
const fuzzySearchGroupsResults = power_user.fuzzy_search ? fuzzySearchGroups(searchValue) : [];
|
||||
|
||||
function getIsValidSearch(_this) {
|
||||
const name = $(_this).find(".ch_name").text().toLowerCase();
|
||||
const chid = $(_this).attr("chid");
|
||||
const grid = $(_this).attr("grid");
|
||||
|
||||
if (power_user.fuzzy_search) {
|
||||
if (chid !== undefined) {
|
||||
return fuzzySearchCharactersResults.includes(parseInt(chid));
|
||||
} else if (grid !== undefined) {
|
||||
return fuzzySearchGroupsResults.includes(String(grid));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return name.includes(searchValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (!searchValue) {
|
||||
$(selector).removeClass('hiddenBySearch');
|
||||
updateVisibleDivs('#rm_print_characters_block', true);
|
||||
} else {
|
||||
$(selector).each(function () {
|
||||
const isValidSearch = getIsValidSearch(this);
|
||||
$(this).toggleClass('hiddenBySearch', !isValidSearch);
|
||||
});
|
||||
updateVisibleDivs('#rm_print_characters_block', true);
|
||||
}
|
||||
updateCharacterCount(selector);
|
||||
|
||||
|
||||
const searchValue = $(this).val().toLowerCase();
|
||||
entitiesFilter.setFilterData(FILTER_TYPES.SEARCH, searchValue);
|
||||
});
|
||||
|
||||
$("#send_but").click(function () {
|
||||
@ -7406,32 +7518,11 @@ $(document).ready(function () {
|
||||
$("#character_search_bar").val("").trigger("input");
|
||||
});
|
||||
|
||||
$(document).on("click", ".character_select", function () {
|
||||
if (selected_group && is_group_generating) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selected_group || this_chid !== $(this).attr("chid")) {
|
||||
//if clicked on a different character from what was currently selected
|
||||
if (!is_send_press) {
|
||||
cancelTtsPlay();
|
||||
resetSelectedGroup();
|
||||
this_edit_mes_id = undefined;
|
||||
selected_button = "character_edit";
|
||||
this_chid = $(this).attr("chid");
|
||||
clearChat();
|
||||
chat.length = 0;
|
||||
chat_metadata = {};
|
||||
getChat();
|
||||
}
|
||||
} else {
|
||||
//if clicked on character that was already selected
|
||||
selected_button = "character_edit";
|
||||
select_selected_character(this_chid);
|
||||
}
|
||||
$(document).on("click", ".character_select", function() {
|
||||
const id = $(this).attr("chid");
|
||||
selectCharacterById(id);
|
||||
});
|
||||
|
||||
|
||||
$(document).on("input", ".edit_textarea", function () {
|
||||
scroll_holder = $("#chat").scrollTop();
|
||||
$(this).height(0).height(this.scrollHeight);
|
||||
@ -7638,7 +7729,7 @@ $(document).ready(function () {
|
||||
setTimeout(function () {
|
||||
$("#option_select_chat").click();
|
||||
$("#options").hide();
|
||||
}, 200);
|
||||
}, 2000);
|
||||
}
|
||||
if (popup_type == "del_ch") {
|
||||
const deleteChats = !!$("#del_char_checkbox").prop("checked");
|
||||
@ -7737,11 +7828,6 @@ $(document).ready(function () {
|
||||
);
|
||||
});
|
||||
|
||||
$("#rm_info_button").on('click', function () {
|
||||
$("#rm_info_avatar").html("");
|
||||
select_rm_characters();
|
||||
});
|
||||
|
||||
//////// OPTIMIZED ALL CHAR CREATION/EDITING TEXTAREA LISTENERS ///////////////
|
||||
|
||||
$("#character_name_pole").on("input", function () {
|
||||
@ -9145,8 +9231,7 @@ $(document).ready(function () {
|
||||
doCharListDisplaySwitch();
|
||||
});
|
||||
|
||||
$("#hideCharPanelAvatarButton").on('click', () => {
|
||||
$("#hideCharPanelAvatarButton").hide().on('click', () => {
|
||||
$('#avatar-and-name-block').slideToggle()
|
||||
})
|
||||
|
||||
});
|
||||
});
|
||||
|
@ -1,8 +1,8 @@
|
||||
import {callPopup, event_types, eventSource, is_send_press, main_api, substituteParams} from "../script.js";
|
||||
import { callPopup, event_types, eventSource, is_send_press, main_api, substituteParams } from "../script.js";
|
||||
import { is_group_generating } from "./group-chats.js";
|
||||
import {TokenHandler} from "./openai.js";
|
||||
import {power_user} from "./power-user.js";
|
||||
import { debounce, getSortableDelay, waitUntilCondition } from "./utils.js";
|
||||
import { TokenHandler } from "./openai.js";
|
||||
import { power_user } from "./power-user.js";
|
||||
import { debounce, waitUntilCondition } from "./utils.js";
|
||||
|
||||
function debouncePromise(func, delay) {
|
||||
let timeoutId;
|
||||
@ -70,7 +70,7 @@ class Prompt {
|
||||
* @param {string} param0.name - The name of the prompt.
|
||||
* @param {boolean} param0.system_prompt - Indicates if the prompt is a system prompt.
|
||||
*/
|
||||
constructor({identifier, role, content, name, system_prompt} = {}) {
|
||||
constructor({ identifier, role, content, name, system_prompt } = {}) {
|
||||
this.identifier = identifier;
|
||||
this.role = role;
|
||||
this.content = content;
|
||||
@ -101,8 +101,8 @@ class PromptCollection {
|
||||
* @throws Will throw an error if one or more instances are not of the Prompt class.
|
||||
*/
|
||||
checkPromptInstance(...prompts) {
|
||||
for(let prompt of prompts) {
|
||||
if(!(prompt instanceof Prompt)) {
|
||||
for (let prompt of prompts) {
|
||||
if (!(prompt instanceof Prompt)) {
|
||||
throw new Error('Only Prompt instances can be added to PromptCollection');
|
||||
}
|
||||
}
|
||||
@ -168,7 +168,11 @@ function PromptManagerModule() {
|
||||
listIdentifier: '',
|
||||
listItemTemplateIdentifier: '',
|
||||
toggleDisabled: [],
|
||||
draggable: true,
|
||||
promptOrder: {
|
||||
strategy: 'global',
|
||||
dummyId: 100000
|
||||
},
|
||||
sortableDelay: 30,
|
||||
warningTokenThreshold: 1500,
|
||||
dangerTokenThreshold: 500,
|
||||
defaultPrompts: {
|
||||
@ -246,7 +250,7 @@ function PromptManagerModule() {
|
||||
this.handleCharacterExport = () => { };
|
||||
|
||||
/** Character reset button click*/
|
||||
this.handleCharacterReset = () => {};
|
||||
this.handleCharacterReset = () => { };
|
||||
|
||||
/** Debounced version of render */
|
||||
this.renderDebounced = debounce(this.render.bind(this), 1000);
|
||||
@ -378,7 +382,7 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
const promptID = document.getElementById(this.configuration.prefix + 'prompt_manager_footer_append_prompt').value;
|
||||
const prompt = this.getPromptById(promptID);
|
||||
|
||||
if (prompt){
|
||||
if (prompt) {
|
||||
this.appendPrompt(prompt, this.activeCharacter);
|
||||
this.saveServiceSettings().then(() => this.render());
|
||||
}
|
||||
@ -386,7 +390,7 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
|
||||
// Delete selected prompt from list form and close edit form
|
||||
this.handleDeletePrompt = (event) => {
|
||||
const promptID = document.getElementById(this.configuration.prefix + 'prompt_manager_footer_append_prompt').value;
|
||||
const promptID = document.getElementById(this.configuration.prefix + 'prompt_manager_footer_append_prompt').value;
|
||||
const prompt = this.getPromptById(promptID);
|
||||
|
||||
if (prompt && true === this.isPromptDeletionAllowed(prompt)) {
|
||||
@ -416,12 +420,26 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
|
||||
// Export all user prompts
|
||||
this.handleFullExport = () => {
|
||||
const exportPrompts = this.serviceSettings.prompts.reduce((userPrompts, prompt) => {
|
||||
const prompts = this.serviceSettings.prompts.reduce((userPrompts, prompt) => {
|
||||
if (false === prompt.system_prompt && false === prompt.marker) userPrompts.push(prompt);
|
||||
return userPrompts;
|
||||
}, []);
|
||||
|
||||
this.export({prompts: exportPrompts}, 'full', 'st-prompts');
|
||||
let promptOrder = [];
|
||||
if ('global' === this.configuration.promptOrder.strategy) {
|
||||
promptOrder = this.getPromptOrderForCharacter({ id: this.configuration.promptOrder.dummyId });
|
||||
} else if ('character' === this.configuration.promptOrder.strategy) {
|
||||
promptOrder = [];
|
||||
} else {
|
||||
throw new Error('Prompt order strategy not supported.')
|
||||
}
|
||||
|
||||
const exportPrompts = {
|
||||
prompts: prompts,
|
||||
prompt_order: promptOrder
|
||||
}
|
||||
|
||||
this.export(exportPrompts, 'full', 'st-prompts');
|
||||
}
|
||||
|
||||
// Export user prompts and order for this character
|
||||
@ -472,10 +490,10 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
});
|
||||
});
|
||||
|
||||
fileOpener.click();
|
||||
});
|
||||
fileOpener.click();
|
||||
});
|
||||
}
|
||||
|
||||
// Restore default state of a characters prompt order
|
||||
@ -538,7 +556,7 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_save').addEventListener('click', this.handleSavePrompt);
|
||||
document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_reset').addEventListener('click', this.handleResetPrompt);
|
||||
|
||||
const closeAndClearPopup = () => {
|
||||
const closeAndClearPopup = () => {
|
||||
this.hidePopup();
|
||||
this.clearEditForm();
|
||||
this.clearInspectForm();
|
||||
@ -554,7 +572,7 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
this.saveServiceSettings().then(() => {
|
||||
this.hidePopup();
|
||||
this.clearEditForm();
|
||||
this.renderDebounced()
|
||||
this.renderDebounced();
|
||||
});
|
||||
});
|
||||
|
||||
@ -634,7 +652,7 @@ PromptManagerModule.prototype.updatePrompts = function (prompts) {
|
||||
})
|
||||
}
|
||||
|
||||
PromptManagerModule.prototype.getTokenHandler = function() {
|
||||
PromptManagerModule.prototype.getTokenHandler = function () {
|
||||
return this.tokenHandler;
|
||||
}
|
||||
|
||||
@ -648,7 +666,7 @@ PromptManagerModule.prototype.appendPrompt = function (prompt, character) {
|
||||
const promptOrder = this.getPromptOrderForCharacter(character);
|
||||
const index = promptOrder.findIndex(entry => entry.identifier === prompt.identifier);
|
||||
|
||||
if (-1 === index) promptOrder.push({identifier: prompt.identifier, enabled: false});
|
||||
if (-1 === index) promptOrder.push({ identifier: prompt.identifier, enabled: false });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -694,6 +712,13 @@ PromptManagerModule.prototype.sanitizeServiceSettings = function () {
|
||||
this.serviceSettings.prompts = this.serviceSettings.prompts ?? [];
|
||||
this.serviceSettings.prompt_order = this.serviceSettings.prompt_order ?? [];
|
||||
|
||||
if ('global' === this.configuration.promptOrder.strategy) {
|
||||
const dummyCharacter = { id: this.configuration.promptOrder.dummyId };
|
||||
const promptOrder = this.getPromptOrderForCharacter(dummyCharacter);
|
||||
|
||||
if (0 === promptOrder.length) this.addPromptOrderForCharacter(dummyCharacter, promptManagerDefaultPromptOrder);
|
||||
}
|
||||
|
||||
// Check whether the referenced prompts are present.
|
||||
this.serviceSettings.prompts.length === 0
|
||||
? this.setPrompts(chatCompletionDefaultPrompts.prompts)
|
||||
@ -704,11 +729,11 @@ PromptManagerModule.prototype.sanitizeServiceSettings = function () {
|
||||
|
||||
if (this.activeCharacter) {
|
||||
const promptReferences = this.getPromptOrderForCharacter(this.activeCharacter);
|
||||
for(let i = promptReferences.length - 1; i >= 0; i--) {
|
||||
const reference = promptReferences[i];
|
||||
if(-1 === this.serviceSettings.prompts.findIndex(prompt => prompt.identifier === reference.identifier)) {
|
||||
for (let i = promptReferences.length - 1; i >= 0; i--) {
|
||||
const reference = promptReferences[i];
|
||||
if (-1 === this.serviceSettings.prompts.findIndex(prompt => prompt.identifier === reference.identifier)) {
|
||||
promptReferences.splice(i, 1);
|
||||
this.log('Removed unused reference: ' + reference.identifier);
|
||||
this.log('Removed unused reference: ' + reference.identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -720,11 +745,11 @@ PromptManagerModule.prototype.sanitizeServiceSettings = function () {
|
||||
*
|
||||
* @param prompts
|
||||
*/
|
||||
PromptManagerModule.prototype.checkForMissingPrompts = function(prompts) {
|
||||
const defaultPromptIdentifiers = chatCompletionDefaultPrompts.prompts.reduce((list, prompt) => { list.push(prompt.identifier); return list;}, []);
|
||||
PromptManagerModule.prototype.checkForMissingPrompts = function (prompts) {
|
||||
const defaultPromptIdentifiers = chatCompletionDefaultPrompts.prompts.reduce((list, prompt) => { list.push(prompt.identifier); return list; }, []);
|
||||
|
||||
const missingIdentifiers = defaultPromptIdentifiers.filter(identifier =>
|
||||
!prompts.some(prompt =>prompt.identifier === identifier)
|
||||
!prompts.some(prompt => prompt.identifier === identifier)
|
||||
);
|
||||
|
||||
missingIdentifiers.forEach(identifier => {
|
||||
@ -775,9 +800,10 @@ PromptManagerModule.prototype.isPromptToggleAllowed = function (prompt) {
|
||||
/**
|
||||
* Handle the deletion of a character by removing their prompt list and nullifying the active character if it was the one deleted.
|
||||
* @param {object} event - The event object containing the character's ID.
|
||||
* @returns boolean
|
||||
* @returns void
|
||||
*/
|
||||
PromptManagerModule.prototype.handleCharacterDeleted = function (event) {
|
||||
if ('global' === this.configuration.promptOrder.strategy) return;
|
||||
this.removePromptOrderForCharacter(this.activeCharacter);
|
||||
if (this.activeCharacter.id === event.detail.id) this.activeCharacter = null;
|
||||
}
|
||||
@ -788,12 +814,19 @@ PromptManagerModule.prototype.handleCharacterDeleted = function (event) {
|
||||
* @returns {void}
|
||||
*/
|
||||
PromptManagerModule.prototype.handleCharacterSelected = function (event) {
|
||||
this.activeCharacter = {id: event.detail.id, ...event.detail.character};
|
||||
const promptOrder = this.getPromptOrderForCharacter(this.activeCharacter);
|
||||
if ('global' === this.configuration.promptOrder.strategy) {
|
||||
this.activeCharacter = { id: this.configuration.promptOrder.dummyId };
|
||||
} else if ('character' === this.configuration.promptOrder.strategy) {
|
||||
console.log('FOO')
|
||||
this.activeCharacter = { id: event.detail.id, ...event.detail.character };
|
||||
const promptOrder = this.getPromptOrderForCharacter(this.activeCharacter);
|
||||
|
||||
// ToDo: These should be passed as parameter or attached to the manager as a set of default options.
|
||||
// Set default prompts and order for character.
|
||||
if (0 === promptOrder.length) this.addPromptOrderForCharacter(this.activeCharacter, promptManagerDefaultPromptOrder);
|
||||
// ToDo: These should be passed as parameter or attached to the manager as a set of default options.
|
||||
// Set default prompts and order for character.
|
||||
if (0 === promptOrder.length) this.addPromptOrderForCharacter(this.activeCharacter, promptManagerDefaultPromptOrder);
|
||||
} else {
|
||||
throw new Error('Unsupported prompt order mode.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -802,7 +835,13 @@ PromptManagerModule.prototype.handleCharacterSelected = function (event) {
|
||||
* @param event
|
||||
*/
|
||||
PromptManagerModule.prototype.handleCharacterUpdated = function (event) {
|
||||
this.activeCharacter = {id: event.detail.id, ...event.detail.character};
|
||||
if ('global' === this.configuration.promptOrder.strategy) {
|
||||
this.activeCharacter = { id: this.configuration.promptOrder.dummyId };
|
||||
} else if ('character' === this.configuration.promptOrder.strategy) {
|
||||
this.activeCharacter = { id: event.detail.id, ...event.detail.character };
|
||||
} else {
|
||||
throw new Error('Prompt order strategy not supported.')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -811,11 +850,17 @@ PromptManagerModule.prototype.handleCharacterUpdated = function (event) {
|
||||
* @param event
|
||||
*/
|
||||
PromptManagerModule.prototype.handleGroupSelected = function (event) {
|
||||
const characterDummy = {id: event.detail.id, group: event.detail.group};
|
||||
this.activeCharacter = characterDummy;
|
||||
const promptOrder = this.getPromptOrderForCharacter(characterDummy);
|
||||
if ('global' === this.configuration.promptOrder.strategy) {
|
||||
this.activeCharacter = { id: this.configuration.promptOrder.dummyId };
|
||||
} else if ('character' === this.configuration.promptOrder.strategy) {
|
||||
const characterDummy = { id: event.detail.id, group: event.detail.group };
|
||||
this.activeCharacter = characterDummy;
|
||||
const promptOrder = this.getPromptOrderForCharacter(characterDummy);
|
||||
|
||||
if (0 === promptOrder.length) this.addPromptOrderForCharacter(characterDummy, promptManagerDefaultPromptOrder)
|
||||
if (0 === promptOrder.length) this.addPromptOrderForCharacter(characterDummy, promptManagerDefaultPromptOrder)
|
||||
} else {
|
||||
throw new Error('Prompt order strategy not supported.')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -823,9 +868,9 @@ PromptManagerModule.prototype.handleGroupSelected = function (event) {
|
||||
*
|
||||
* @returns {string[]}
|
||||
*/
|
||||
PromptManagerModule.prototype.getActiveGroupCharacters = function() {
|
||||
PromptManagerModule.prototype.getActiveGroupCharacters = function () {
|
||||
// ToDo: Ideally, this should return the actual characters.
|
||||
return (this.activeCharacter?.group?.members || []).map(member => member.substring(0, member.lastIndexOf('.')));
|
||||
return (this.activeCharacter?.group?.members || []).map(member => member && member.substring(0, member.lastIndexOf('.')));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -937,7 +982,7 @@ PromptManagerModule.prototype.preparePrompt = function (prompt, original = null)
|
||||
* and handle input events to update the prompt content.
|
||||
*
|
||||
*/
|
||||
PromptManagerModule.prototype.createQuickEdit = function(identifier, title) {
|
||||
PromptManagerModule.prototype.createQuickEdit = function (identifier, title) {
|
||||
const prompt = this.getPromptById(identifier);
|
||||
const textareaIdentifier = `${identifier}_prompt_quick_edit_textarea`;
|
||||
const html = `<div class="range-block m-t-1">
|
||||
@ -961,7 +1006,7 @@ PromptManagerModule.prototype.createQuickEdit = function(identifier, title) {
|
||||
|
||||
}
|
||||
|
||||
PromptManagerModule.prototype.updateQuickEdit = function(identifier, prompt) {
|
||||
PromptManagerModule.prototype.updateQuickEdit = function (identifier, prompt) {
|
||||
const textarea = document.getElementById(`${identifier}_prompt_quick_edit_textarea`);
|
||||
textarea.value = prompt.content;
|
||||
}
|
||||
@ -973,13 +1018,13 @@ PromptManagerModule.prototype.updateQuickEdit = function(identifier, prompt) {
|
||||
* @param name
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PromptManagerModule.prototype.isValidName = function(name) {
|
||||
PromptManagerModule.prototype.isValidName = function (name) {
|
||||
const regex = /^[a-zA-Z0-9_]{1,64}$/;
|
||||
|
||||
return regex.test(name);
|
||||
}
|
||||
|
||||
PromptManagerModule.prototype.sanitizeName = function(name) {
|
||||
PromptManagerModule.prototype.sanitizeName = function (name) {
|
||||
return name.replace(/[^a-zA-Z0-9_]/g, '_').substring(0, 64);
|
||||
}
|
||||
|
||||
@ -1066,7 +1111,7 @@ PromptManagerModule.prototype.clearEditForm = function () {
|
||||
roleField.disabled = false;
|
||||
}
|
||||
|
||||
PromptManagerModule.prototype.clearInspectForm = function() {
|
||||
PromptManagerModule.prototype.clearInspectForm = function () {
|
||||
const inspectArea = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_inspect');
|
||||
inspectArea.style.display = 'none';
|
||||
const messageList = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_inspect_list');
|
||||
@ -1105,7 +1150,7 @@ PromptManagerModule.prototype.setMessages = function (messages) {
|
||||
*
|
||||
* @param {ChatCompletion} chatCompletion
|
||||
*/
|
||||
PromptManagerModule.prototype.setChatCompletion = function(chatCompletion) {
|
||||
PromptManagerModule.prototype.setChatCompletion = function (chatCompletion) {
|
||||
const messages = chatCompletion.getMessages();
|
||||
|
||||
this.setMessages(messages);
|
||||
@ -1118,7 +1163,7 @@ PromptManagerModule.prototype.setChatCompletion = function(chatCompletion) {
|
||||
*
|
||||
* @param {MessageCollection} messages
|
||||
*/
|
||||
PromptManagerModule.prototype.populateTokenCounts = function(messages) {
|
||||
PromptManagerModule.prototype.populateTokenCounts = function (messages) {
|
||||
this.tokenHandler.resetCounts();
|
||||
const counts = this.tokenHandler.getCounts();
|
||||
messages.getCollection().forEach(message => {
|
||||
@ -1137,7 +1182,7 @@ PromptManagerModule.prototype.populateTokenCounts = function(messages) {
|
||||
*
|
||||
* @param {MessageCollection} messages
|
||||
*/
|
||||
PromptManagerModule.prototype.populateLegacyTokenCounts = function(messages) {
|
||||
PromptManagerModule.prototype.populateLegacyTokenCounts = function (messages) {
|
||||
// Update general token counts
|
||||
const chatHistory = messages.getItemByIdentifier('chatHistory');
|
||||
const startChat = chatHistory?.getCollection()[0].getTokens() || 0;
|
||||
@ -1201,8 +1246,8 @@ PromptManagerModule.prototype.renderPromptManager = function () {
|
||||
</select>
|
||||
<a class="menu_button fa-chain fa-solid" title="Insert prompt" data-i18n="Insert"></a>
|
||||
<a class="caution menu_button fa-x fa-solid" title="Delete prompt" data-i18n="Delete"></a>
|
||||
<a class="menu_button fa-file-arrow-down fa-solid" id="prompt-manager-export" title="Export this prompt list" data-i18n="Export"></a>
|
||||
<a class="menu_button fa-file-arrow-up fa-solid" id="prompt-manager-import" title="Import a prompt list" data-i18n="Import"></a>
|
||||
<a class="menu_button fa-file-import fa-solid" id="prompt-manager-import" title="Import a prompt list" data-i18n="Import"></a>
|
||||
<a class="menu_button fa-file-export fa-solid" id="prompt-manager-export" title="Export this prompt list" data-i18n="Export"></a>
|
||||
<a class="menu_button fa-undo fa-solid" id="prompt-manager-reset-character" title="Reset current character" data-i18n="Reset current character"></a>
|
||||
<a class="menu_button fa-plus-square fa-solid" title="New prompt" data-i18n="New"></a>
|
||||
</div>
|
||||
@ -1225,10 +1270,14 @@ PromptManagerModule.prototype.renderPromptManager = function () {
|
||||
<a class="export-promptmanager-prompts-full list-group-item" data-i18n="Export all">Export all</a>
|
||||
<span class="tooltip fa-solid fa-info-circle" title="Export all your prompts to a file"></span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a class="export-promptmanager-prompts-character list-group-item" data-i18n="Export for character">Export for character</a>
|
||||
<span class="tooltip fa-solid fa-info-circle" title="Export prompts for this character, including their order."></span>
|
||||
</div>
|
||||
${'global' === this.configuration.promptOrder.strategy
|
||||
? ''
|
||||
: `<div class="row">
|
||||
<a class="export-promptmanager-prompts-character list-group-item" data-i18n="Export for character">Export
|
||||
for character</a>
|
||||
<span class="tooltip fa-solid fa-info-circle"
|
||||
title="Export prompts for this character, including their order."></span>
|
||||
</div>` }
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@ -1238,7 +1287,7 @@ PromptManagerModule.prototype.renderPromptManager = function () {
|
||||
let exportPopper = Popper.createPopper(
|
||||
document.getElementById('prompt-manager-export'),
|
||||
document.getElementById('prompt-manager-export-format-popup'),
|
||||
{placement: 'bottom'}
|
||||
{ placement: 'bottom' }
|
||||
);
|
||||
|
||||
const showExportSelection = () => {
|
||||
@ -1254,17 +1303,49 @@ PromptManagerModule.prototype.renderPromptManager = function () {
|
||||
footerDiv.querySelector('#prompt-manager-import').addEventListener('click', this.handleImport);
|
||||
footerDiv.querySelector('#prompt-manager-export').addEventListener('click', showExportSelection);
|
||||
rangeBlockDiv.querySelector('.export-promptmanager-prompts-full').addEventListener('click', this.handleFullExport);
|
||||
rangeBlockDiv.querySelector('.export-promptmanager-prompts-character').addEventListener('click', this.handleCharacterExport);
|
||||
rangeBlockDiv.querySelector('.export-promptmanager-prompts-character')?.addEventListener('click', this.handleCharacterExport);
|
||||
|
||||
const quickEditContainer = document.getElementById('quick-edit-container');
|
||||
const heights = this.saveTextAreaHeights(quickEditContainer);
|
||||
quickEditContainer.innerHTML = '';
|
||||
|
||||
this.createQuickEdit('jailbreak', 'Jailbreak');
|
||||
this.createQuickEdit('nsfw', 'NSFW');
|
||||
this.createQuickEdit('main', 'Main');
|
||||
|
||||
this.restoreTextAreaHeights(quickEditContainer, heights);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Restores the height of each textarea in the container
|
||||
* @param container The container to search for textareas
|
||||
* @param heights An object with textarea ids as keys and heights as values
|
||||
*/
|
||||
PromptManagerModule.prototype.restoreTextAreaHeights = function(container, heights) {
|
||||
if (Object.keys(heights).length === 0) return;
|
||||
|
||||
$(container).find('textarea').each(function () {
|
||||
const height = heights[this.id];
|
||||
if (height > 0) $(this).height(height);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the current height of each textarea in the container
|
||||
* @param container The container to search for textareas
|
||||
* @returns {{}} An object with textarea ids as keys and heights as values
|
||||
*/
|
||||
PromptManagerModule.prototype.saveTextAreaHeights = function(container) {
|
||||
const heights = {};
|
||||
|
||||
$(container).find('textarea').each(function () {
|
||||
heights[this.id] = $(this).height();
|
||||
});
|
||||
|
||||
return heights;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empties, then re-assembles the prompt list
|
||||
*/
|
||||
@ -1274,7 +1355,7 @@ PromptManagerModule.prototype.renderPromptManagerListItems = function () {
|
||||
const promptManagerList = this.listElement;
|
||||
promptManagerList.innerHTML = '';
|
||||
|
||||
const {prefix} = this.configuration;
|
||||
const { prefix } = this.configuration;
|
||||
|
||||
let listItemHtml = `
|
||||
<li class="${prefix}prompt_manager_list_head">
|
||||
@ -1301,7 +1382,7 @@ PromptManagerModule.prototype.renderPromptManagerListItems = function () {
|
||||
let warningTitle = '';
|
||||
|
||||
const tokenBudget = this.serviceSettings.openai_max_context - this.serviceSettings.openai_max_tokens;
|
||||
if ( this.tokenUsage > tokenBudget * 0.8 &&
|
||||
if (this.tokenUsage > tokenBudget * 0.8 &&
|
||||
'chatHistory' === prompt.identifier) {
|
||||
const warningThreshold = this.configuration.warningTokenThreshold;
|
||||
const dangerThreshold = this.configuration.dangerTokenThreshold;
|
||||
@ -1350,7 +1431,7 @@ PromptManagerModule.prototype.renderPromptManagerListItems = function () {
|
||||
${prompt.marker ? '<span class="fa-solid fa-thumb-tack" title="Marker"></span>' : ''}
|
||||
${!prompt.marker && prompt.system_prompt ? '<span class="fa-solid fa-square-poll-horizontal" title="Global Prompt"></span>' : ''}
|
||||
${!prompt.marker && !prompt.system_prompt ? '<span class="fa-solid fa-user" title="User Prompt"></span>' : ''}
|
||||
${this.isPromptInspectionAllowed(prompt) ? `<a class="prompt-manager-inspect-action">${prompt.name}</a>` : prompt.name }
|
||||
${this.isPromptInspectionAllowed(prompt) ? `<a class="prompt-manager-inspect-action">${prompt.name}</a>` : prompt.name}
|
||||
</span>
|
||||
<span>
|
||||
<span class="prompt_manager_prompt_controls">
|
||||
@ -1400,7 +1481,7 @@ PromptManagerModule.prototype.export = function (data, type, name = 'export') {
|
||||
};
|
||||
|
||||
const serializedObject = JSON.stringify(promptExport);
|
||||
const blob = new Blob([serializedObject], {type: "application/json"});
|
||||
const blob = new Blob([serializedObject], { type: "application/json" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const downloadLink = document.createElement('a');
|
||||
downloadLink.href = url;
|
||||
@ -1451,10 +1532,19 @@ PromptManagerModule.prototype.import = function (importData) {
|
||||
this.setPrompts(prompts);
|
||||
this.log('Prompt import succeeded');
|
||||
|
||||
if ('character' === importData.type) {
|
||||
const promptOrder = this.getPromptOrderForCharacter(this.activeCharacter);
|
||||
let promptOrder = [];
|
||||
if ('global' === this.configuration.promptOrder.strategy) {
|
||||
const promptOrder = this.getPromptOrderForCharacter({ id: this.configuration.promptOrder.dummyId });
|
||||
Object.assign(promptOrder, importData.data.prompt_order);
|
||||
this.log(`Prompt order import for character ${this.activeCharacter.name} completed`);
|
||||
this.log(`Prompt order import succeeded`);
|
||||
} else if ('character' === this.configuration.promptOrder.strategy) {
|
||||
if ('character' === importData.type) {
|
||||
const promptOrder = this.getPromptOrderForCharacter(this.activeCharacter);
|
||||
Object.assign(promptOrder, importData.data.prompt_order);
|
||||
this.log(`Prompt order import for character ${this.activeCharacter.name} succeeded`);
|
||||
}
|
||||
} else {
|
||||
throw new Error('Prompt order strategy not supported.')
|
||||
}
|
||||
|
||||
toastr.success('Prompt import complete.');
|
||||
@ -1468,7 +1558,7 @@ PromptManagerModule.prototype.import = function (importData) {
|
||||
* @param object
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PromptManagerModule.prototype.validateObject = function(controlObj, object) {
|
||||
PromptManagerModule.prototype.validateObject = function (controlObj, object) {
|
||||
for (let key in controlObj) {
|
||||
if (!object.hasOwnProperty(key)) {
|
||||
if (controlObj[key] === null) continue;
|
||||
@ -1491,7 +1581,7 @@ PromptManagerModule.prototype.validateObject = function(controlObj, object) {
|
||||
*
|
||||
* @returns {`${string}_${string}_${string}`}
|
||||
*/
|
||||
PromptManagerModule.prototype.getFormattedDate = function() {
|
||||
PromptManagerModule.prototype.getFormattedDate = function () {
|
||||
const date = new Date();
|
||||
let month = String(date.getMonth() + 1);
|
||||
let day = String(date.getDate());
|
||||
@ -1511,11 +1601,11 @@ PromptManagerModule.prototype.getFormattedDate = function() {
|
||||
*/
|
||||
PromptManagerModule.prototype.makeDraggable = function () {
|
||||
$(`#${this.configuration.prefix}prompt_manager_list`).sortable({
|
||||
delay: getSortableDelay(),
|
||||
delay: this.configuration.sortableDelay,
|
||||
items: `.${this.configuration.prefix}prompt_manager_prompt_draggable`,
|
||||
update: ( event, ui ) => {
|
||||
update: (event, ui) => {
|
||||
const promptOrder = this.getPromptOrderForCharacter(this.activeCharacter);
|
||||
const promptListElement = $(`#${this.configuration.prefix}prompt_manager_list`).sortable('toArray', {attribute: 'data-pm-identifier'});
|
||||
const promptListElement = $(`#${this.configuration.prefix}prompt_manager_list`).sortable('toArray', { attribute: 'data-pm-identifier' });
|
||||
const idToObjectMap = new Map(promptOrder.map(prompt => [prompt.identifier, prompt]));
|
||||
const updatedPromptOrder = promptListElement.map(identifier => idToObjectMap.get(identifier));
|
||||
|
||||
@ -1525,7 +1615,8 @@ PromptManagerModule.prototype.makeDraggable = function () {
|
||||
this.log(`Prompt order updated for ${this.activeCharacter.name}.`);
|
||||
|
||||
this.saveServiceSettings();
|
||||
}});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1536,7 +1627,7 @@ PromptManagerModule.prototype.showPopup = function (area = 'edit') {
|
||||
const areaElement = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_' + area);
|
||||
areaElement.style.display = 'block';
|
||||
|
||||
$('#'+this.configuration.prefix +'prompt_manager_popup').first()
|
||||
$('#' + this.configuration.prefix + 'prompt_manager_popup').first()
|
||||
.slideDown(200, "swing")
|
||||
.addClass('openDrawer');
|
||||
}
|
||||
@ -1546,7 +1637,7 @@ PromptManagerModule.prototype.showPopup = function (area = 'edit') {
|
||||
* @returns {void}
|
||||
*/
|
||||
PromptManagerModule.prototype.hidePopup = function () {
|
||||
$('#'+this.configuration.prefix +'prompt_manager_popup').first()
|
||||
$('#' + this.configuration.prefix + 'prompt_manager_popup').first()
|
||||
.slideUp(200, "swing")
|
||||
.removeClass('openDrawer');
|
||||
}
|
||||
|
@ -13,11 +13,13 @@ import {
|
||||
menu_type,
|
||||
max_context,
|
||||
saveSettingsDebounced,
|
||||
eventSource,
|
||||
active_group,
|
||||
active_character,
|
||||
setActiveGroup,
|
||||
setActiveCharacter,
|
||||
getEntitiesList,
|
||||
getThumbnailUrl,
|
||||
selectCharacterById,
|
||||
} from "../script.js";
|
||||
|
||||
import {
|
||||
@ -31,12 +33,12 @@ import {
|
||||
} from "./power-user.js";
|
||||
|
||||
import { LoadLocal, SaveLocal, CheckLocal, LoadLocalBool } from "./f-localStorage.js";
|
||||
import { selected_group, is_group_generating, getGroupAvatar, groups } from "./group-chats.js";
|
||||
import { selected_group, is_group_generating, getGroupAvatar, groups, openGroupById } from "./group-chats.js";
|
||||
import {
|
||||
SECRET_KEYS,
|
||||
secret_state,
|
||||
} from "./secrets.js";
|
||||
import { sortByCssOrder, debounce, delay } from "./utils.js";
|
||||
import { debounce, delay } from "./utils.js";
|
||||
import { chat_completion_sources, oai_settings } from "./openai.js";
|
||||
|
||||
var NavToggle = document.getElementById("nav-toggle");
|
||||
@ -344,17 +346,16 @@ export function RA_CountCharTokens() {
|
||||
* The character or group is selected (clicked) if it is found.
|
||||
*/
|
||||
async function RA_autoloadchat() {
|
||||
if (document.getElementById('CharID0') !== null) {
|
||||
if (document.querySelector('#rm_print_characters_block .character_select') !== null) {
|
||||
// active character is the name, we should look it up in the character list and get the id
|
||||
let active_character_id = Object.keys(characters).find(key => characters[key].avatar === active_character);
|
||||
|
||||
var charToAutoLoad = document.getElementById('CharID' + active_character_id);
|
||||
let groupToAutoLoad = document.querySelector(`.group_select[grid="${active_group}"]`);
|
||||
if (charToAutoLoad != null) {
|
||||
$(charToAutoLoad).click();
|
||||
if (active_character_id !== null) {
|
||||
selectCharacterById(String(active_character_id));
|
||||
}
|
||||
else if (groupToAutoLoad != null) {
|
||||
$(groupToAutoLoad).click();
|
||||
|
||||
if (active_group != null) {
|
||||
openGroupById(String(active_group));
|
||||
}
|
||||
|
||||
// if the character list hadn't been loaded yet, try again.
|
||||
@ -362,53 +363,61 @@ async function RA_autoloadchat() {
|
||||
}
|
||||
|
||||
export async function favsToHotswap() {
|
||||
const selector = ['#rm_print_characters_block .character_select', '#rm_print_characters_block .group_select'].join(',');
|
||||
const entities = getEntitiesList({ doFilter: false });
|
||||
const container = $('#right-nav-panel .hotswap');
|
||||
const template = $('#hotswap_template .hotswapAvatar');
|
||||
container.empty();
|
||||
const maxCount = 6;
|
||||
let count = 0;
|
||||
|
||||
$(selector).sort(sortByCssOrder).each(function () {
|
||||
if ($(this).hasClass('is_fav') && count < maxCount) {
|
||||
const isCharacter = $(this).hasClass('character_select');
|
||||
const isGroup = $(this).hasClass('group_select');
|
||||
const grid = Number($(this).attr('grid'));
|
||||
const chid = Number($(this).attr('chid'));
|
||||
let thisHotSwapSlot = template.clone();
|
||||
thisHotSwapSlot.toggleClass('character_select', isCharacter);
|
||||
thisHotSwapSlot.toggleClass('group_select', isGroup);
|
||||
thisHotSwapSlot.attr('grid', isGroup ? grid : '');
|
||||
thisHotSwapSlot.attr('chid', isCharacter ? chid : '');
|
||||
thisHotSwapSlot.data('id', isGroup ? grid : chid);
|
||||
thisHotSwapSlot.attr('title', '');
|
||||
|
||||
if (isGroup) {
|
||||
const group = groups.find(x => x.id === grid);
|
||||
const avatar = getGroupAvatar(group);
|
||||
$(thisHotSwapSlot).find('img').replaceWith(avatar);
|
||||
}
|
||||
|
||||
if (isCharacter) {
|
||||
const avatarUrl = $(this).find('img').attr('src');
|
||||
$(thisHotSwapSlot).find('img').attr('src', avatarUrl);
|
||||
}
|
||||
|
||||
$(thisHotSwapSlot).css('cursor', 'pointer');
|
||||
container.append(thisHotSwapSlot);
|
||||
count++;
|
||||
for (const entity of entities) {
|
||||
if (count >= maxCount) {
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
//console.log('about to check for leftover selectors...')
|
||||
const isFavorite = entity.item.fav || entity.item.fav == 'true';
|
||||
|
||||
if (!isFavorite) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const isCharacter = entity.type === 'character';
|
||||
const isGroup = entity.type === 'group';
|
||||
|
||||
const grid = isGroup ? entity.id : '';
|
||||
const chid = isCharacter ? entity.id : '';
|
||||
|
||||
let slot = template.clone();
|
||||
slot.toggleClass('character_select', isCharacter);
|
||||
slot.toggleClass('group_select', isGroup);
|
||||
slot.attr('grid', isGroup ? grid : '');
|
||||
slot.attr('chid', isCharacter ? chid : '');
|
||||
slot.data('id', isGroup ? grid : chid);
|
||||
|
||||
if (isGroup) {
|
||||
const group = groups.find(x => x.id === grid);
|
||||
const avatar = getGroupAvatar(group);
|
||||
$(slot).find('img').replaceWith(avatar);
|
||||
$(slot).attr('title', group.name);
|
||||
}
|
||||
|
||||
if (isCharacter) {
|
||||
const avatarUrl = getThumbnailUrl('avatar', entity.item.avatar);
|
||||
$(slot).find('img').attr('src', avatarUrl);
|
||||
$(slot).attr('title', entity.item.avatar);
|
||||
}
|
||||
|
||||
$(slot).css('cursor', 'pointer');
|
||||
container.append(slot);
|
||||
count++;
|
||||
}
|
||||
|
||||
// there are 6 slots in total,
|
||||
if (count < maxCount) { //if any are left over
|
||||
let leftOverSlots = maxCount - count;
|
||||
for (let i = 1; i <= leftOverSlots; i++) {
|
||||
container.append(template.clone());
|
||||
}
|
||||
} else {
|
||||
//console.log(`count was ${count} so no need to knock off any selectors!`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -468,6 +477,7 @@ function RA_autoconnect(PrevApi) {
|
||||
|| (secret_state[SECRET_KEYS.SCALE] && oai_settings.chat_completion_source == chat_completion_sources.SCALE)
|
||||
|| (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI)
|
||||
|| (secret_state[SECRET_KEYS.OPENROUTER] && oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER)
|
||||
|| (secret_state[SECRET_KEYS.AI21] && oai_settings.chat_completion_source == chat_completion_sources.AI21)
|
||||
) {
|
||||
$("#api_button_openai").click();
|
||||
}
|
||||
@ -922,14 +932,16 @@ $("document").ready(function () {
|
||||
|
||||
// when a char is selected from the list, save their name as the auto-load character for next page load
|
||||
$(document).on("click", ".character_select", function () {
|
||||
setActiveCharacter($(this).find('.avatar').attr('title'));
|
||||
const characterId = $(this).find('.avatar').attr('title') || $(this).attr('title');
|
||||
setActiveCharacter(characterId);
|
||||
setActiveGroup(null);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$(document).on("click", ".group_select", function () {
|
||||
const groupId = $(this).data('id') || $(this).attr('grid');
|
||||
setActiveCharacter(null);
|
||||
setActiveGroup($(this).data('id'));
|
||||
setActiveGroup(groupId);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import {
|
||||
characters,
|
||||
saveChat,
|
||||
sendSystemMessage,
|
||||
system_messages,
|
||||
system_message_types,
|
||||
this_chid,
|
||||
@ -19,6 +18,7 @@ import {
|
||||
getGroupPastChats,
|
||||
group_activation_strategy,
|
||||
groups,
|
||||
openGroupById,
|
||||
openGroupChat,
|
||||
saveGroupBookmarkChat,
|
||||
selected_group,
|
||||
@ -301,13 +301,12 @@ async function convertSoloToGroupChat() {
|
||||
}
|
||||
|
||||
// Click on the freshly selected group to open it
|
||||
$(`.group_select[grid="${group.id}"]`).click();
|
||||
await openGroupById(group.id);
|
||||
|
||||
await delay(1);
|
||||
toastr.success('The chat has been successfully converted!');
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
jQuery(function () {
|
||||
$('#option_new_bookmark').on('click', saveBookmarkMenu);
|
||||
$('#option_back_to_main').on('click', backToMainChat);
|
||||
$('#option_convert_to_group').on('click', convertSoloToGroupChat);
|
||||
|
129
public/scripts/filters.js
Normal file
129
public/scripts/filters.js
Normal file
@ -0,0 +1,129 @@
|
||||
import { fuzzySearchCharacters, fuzzySearchGroups, power_user } from "./power-user.js";
|
||||
import { tag_map } from "./tags.js";
|
||||
|
||||
export const FILTER_TYPES = {
|
||||
SEARCH: 'search',
|
||||
TAG: 'tag',
|
||||
FAV: 'fav',
|
||||
GROUP: 'group',
|
||||
};
|
||||
|
||||
export class FilterHelper {
|
||||
constructor(onDataChanged) {
|
||||
this.onDataChanged = onDataChanged;
|
||||
}
|
||||
|
||||
filterFunctions = {
|
||||
[FILTER_TYPES.SEARCH]: this.searchFilter.bind(this),
|
||||
[FILTER_TYPES.GROUP]: this.groupFilter.bind(this),
|
||||
[FILTER_TYPES.FAV]: this.favFilter.bind(this),
|
||||
[FILTER_TYPES.TAG]: this.tagFilter.bind(this),
|
||||
}
|
||||
|
||||
filterData = {
|
||||
[FILTER_TYPES.SEARCH]: '',
|
||||
[FILTER_TYPES.GROUP]: false,
|
||||
[FILTER_TYPES.FAV]: false,
|
||||
[FILTER_TYPES.TAG]: { excluded: [], selected: [] },
|
||||
}
|
||||
|
||||
tagFilter(data) {
|
||||
const TAG_LOGIC_AND = true; // switch to false to use OR logic for combining tags
|
||||
const { selected, excluded } = this.filterData[FILTER_TYPES.TAG];
|
||||
|
||||
if (!selected.length && !excluded.length) {
|
||||
return data;
|
||||
}
|
||||
|
||||
function isElementTagged(entity, tagId) {
|
||||
const isCharacter = entity.type === 'character';
|
||||
const lookupValue = isCharacter ? entity.item.avatar : String(entity.id);
|
||||
const isTagged = Array.isArray(tag_map[lookupValue]) && tag_map[lookupValue].includes(tagId);
|
||||
return isTagged;
|
||||
}
|
||||
|
||||
function getIsTagged(entity) {
|
||||
const tagFlags = selected.map(tagId => isElementTagged(entity, tagId));
|
||||
const trueFlags = tagFlags.filter(x => x);
|
||||
const isTagged = TAG_LOGIC_AND ? tagFlags.length === trueFlags.length : trueFlags.length > 0;
|
||||
|
||||
const excludedTagFlags = excluded.map(tagId => isElementTagged(entity, tagId));
|
||||
const isExcluded = excludedTagFlags.includes(true);
|
||||
|
||||
if (isExcluded) {
|
||||
return false;
|
||||
} else if (selected.length > 0 && !isTagged) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return data.filter(entity => getIsTagged(entity));
|
||||
}
|
||||
|
||||
favFilter(data) {
|
||||
if (!this.filterData[FILTER_TYPES.FAV]) {
|
||||
return data;
|
||||
}
|
||||
|
||||
return data.filter(entity => entity.item.fav || entity.item.fav == "true");
|
||||
}
|
||||
|
||||
groupFilter(data) {
|
||||
if (!this.filterData[FILTER_TYPES.GROUP]) {
|
||||
return data;
|
||||
}
|
||||
|
||||
return data.filter(entity => entity.type === 'group');
|
||||
}
|
||||
|
||||
searchFilter(data) {
|
||||
if (!this.filterData[FILTER_TYPES.SEARCH]) {
|
||||
return data;
|
||||
}
|
||||
|
||||
const searchValue = this.filterData[FILTER_TYPES.SEARCH].trim().toLowerCase();
|
||||
const fuzzySearchCharactersResults = power_user.fuzzy_search ? fuzzySearchCharacters(searchValue) : [];
|
||||
const fuzzySearchGroupsResults = power_user.fuzzy_search ? fuzzySearchGroups(searchValue) : [];
|
||||
|
||||
function getIsValidSearch(entity) {
|
||||
const isGroup = entity.type === 'group';
|
||||
const isCharacter = entity.type === 'character';
|
||||
|
||||
if (power_user.fuzzy_search) {
|
||||
if (isCharacter) {
|
||||
return fuzzySearchCharactersResults.includes(parseInt(entity.id));
|
||||
} else if (isGroup) {
|
||||
return fuzzySearchGroupsResults.includes(String(entity.id));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return entity.item?.name?.toLowerCase()?.includes(searchValue) || false;
|
||||
}
|
||||
}
|
||||
|
||||
return data.filter(entity => getIsValidSearch(entity));
|
||||
}
|
||||
|
||||
setFilterData(filterType, data) {
|
||||
const oldData = this.filterData[filterType];
|
||||
this.filterData[filterType] = data;
|
||||
|
||||
// only trigger a data change if the data actually changed
|
||||
if (JSON.stringify(oldData) !== JSON.stringify(data)) {
|
||||
this.onDataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
getFilterData(filterType) {
|
||||
return this.filterData[filterType];
|
||||
}
|
||||
|
||||
applyFilters(data) {
|
||||
return Object.values(this.filterFunctions)
|
||||
.reduce((data, fn) => fn(data), data);
|
||||
}
|
||||
}
|
@ -7,8 +7,8 @@ import {
|
||||
createThumbnail,
|
||||
extractAllWords,
|
||||
} from './utils.js';
|
||||
import { RA_CountCharTokens, humanizedDateTime, dragElement } from "./RossAscends-mods.js";
|
||||
import { sortCharactersList, sortGroupMembers, loadMovingUIState } from './power-user.js';
|
||||
import { RA_CountCharTokens, humanizedDateTime, dragElement, favsToHotswap } from "./RossAscends-mods.js";
|
||||
import { loadMovingUIState, sortEntitiesList } from './power-user.js';
|
||||
|
||||
import {
|
||||
chat,
|
||||
@ -61,8 +61,10 @@ import {
|
||||
getCurrentChatId,
|
||||
setScenarioOverride,
|
||||
getCropPopup,
|
||||
system_avatar,
|
||||
} from "../script.js";
|
||||
import { appendTagToList, createTagMapFromList, getTagsList, applyTagsOnCharacterSelect, tag_map } from './tags.js';
|
||||
import { appendTagToList, createTagMapFromList, getTagsList, applyTagsOnCharacterSelect, tag_map, printTagFilters } from './tags.js';
|
||||
import { FILTER_TYPES, FilterHelper } from './filters.js';
|
||||
|
||||
export {
|
||||
selected_group,
|
||||
@ -75,7 +77,6 @@ export {
|
||||
deleteGroup,
|
||||
getGroupAvatar,
|
||||
getGroups,
|
||||
printGroups,
|
||||
regenerateGroup,
|
||||
resetSelectedGroup,
|
||||
select_group_chats,
|
||||
@ -87,15 +88,17 @@ let groups = [];
|
||||
let selected_group = null;
|
||||
let group_generation_id = null;
|
||||
let fav_grp_checked = false;
|
||||
let fav_filter_on = false;
|
||||
let openGroupId = null;
|
||||
let newGroupMembers = [];
|
||||
|
||||
export const group_activation_strategy = {
|
||||
NATURAL: 0,
|
||||
LIST: 1,
|
||||
};
|
||||
|
||||
export const groupCandidatesFilter = new FilterHelper(debounce(printGroupCandidates, 100));
|
||||
const groupAutoModeInterval = setInterval(groupChatAutoModeWorker, 5000);
|
||||
const saveGroupDebounced = debounce(async (group) => await _save(group), 500);
|
||||
const saveGroupDebounced = debounce(async (group, reload) => await _save(group, reload), 500);
|
||||
|
||||
async function _save(group, reload = true) {
|
||||
await fetch("/editgroup", {
|
||||
@ -172,6 +175,7 @@ export async function getGroupChat(groupId) {
|
||||
addOneMessage(mes);
|
||||
}
|
||||
}
|
||||
await saveGroupChat(groupId, false);
|
||||
}
|
||||
|
||||
if (group) {
|
||||
@ -179,7 +183,6 @@ export async function getGroupChat(groupId) {
|
||||
updateChatMetadata(metadata, true);
|
||||
}
|
||||
|
||||
await saveGroupChat(groupId, true);
|
||||
eventSource.emit(event_types.CHAT_CHANGED, getCurrentChatId());
|
||||
}
|
||||
|
||||
@ -226,9 +229,8 @@ async function saveGroupChat(groupId, shouldSaveGroup) {
|
||||
});
|
||||
|
||||
if (shouldSaveGroup && response.ok) {
|
||||
await editGroup(groupId);
|
||||
await editGroup(groupId, false, false);
|
||||
}
|
||||
sortCharactersList();
|
||||
}
|
||||
|
||||
export async function renameGroupMember(oldAvatar, newAvatar, newName) {
|
||||
@ -245,7 +247,7 @@ export async function renameGroupMember(oldAvatar, newAvatar, newName) {
|
||||
|
||||
// Replace group member avatar id and save the changes
|
||||
group.members[memberIndex] = newAvatar;
|
||||
await editGroup(group.id, true);
|
||||
await editGroup(group.id, true, false);
|
||||
console.log(`Renamed character ${newName} in group: ${group.name}`)
|
||||
|
||||
// Load all chats from this group
|
||||
@ -306,6 +308,9 @@ async function getGroups() {
|
||||
|
||||
// Convert groups to new format
|
||||
for (const group of groups) {
|
||||
if (typeof group.id === 'number') {
|
||||
group.id = String(group.id);
|
||||
}
|
||||
if (group.disabled_members == undefined) {
|
||||
group.disabled_members = [];
|
||||
}
|
||||
@ -330,8 +335,7 @@ async function getGroups() {
|
||||
}
|
||||
}
|
||||
|
||||
function printGroups() {
|
||||
for (let group of groups) {
|
||||
export function getGroupBlock(group) {
|
||||
const template = $("#group_list_template .group_select").clone();
|
||||
template.data("id", group.id);
|
||||
template.attr("grid", group.id);
|
||||
@ -345,17 +349,20 @@ function printGroups() {
|
||||
const tagsElement = template.find('.tags');
|
||||
tags.forEach(tag => appendTagToList(tagsElement, tag, {}));
|
||||
|
||||
$("#rm_print_characters_block").prepend(template);
|
||||
updateGroupAvatar(group);
|
||||
}
|
||||
const avatar = getGroupAvatar(group);
|
||||
if (avatar) {
|
||||
$(template).find(".avatar").replaceWith(avatar);
|
||||
}
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
function updateGroupAvatar(group) {
|
||||
$("#rm_print_characters_block .group_select").each(function () {
|
||||
$("#group_avatar_preview").empty().append(getGroupAvatar(group));
|
||||
|
||||
$(".group_select").each(function () {
|
||||
if ($(this).data("id") == group.id) {
|
||||
const avatar = getGroupAvatar(group);
|
||||
if (avatar) {
|
||||
$(this).find(".avatar").replaceWith(avatar);
|
||||
}
|
||||
$(this).find(".avatar").replaceWith(getGroupAvatar(group));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -397,7 +404,7 @@ function getGroupAvatar(group) {
|
||||
|
||||
// default avatar
|
||||
const groupAvatar = $("#group_avatars_template .collage_1").clone();
|
||||
groupAvatar.find(".img_1").attr("src", group.avatar_url);
|
||||
groupAvatar.find(".img_1").attr("src", group.avatar_url || system_avatar);
|
||||
return groupAvatar;
|
||||
}
|
||||
|
||||
@ -783,8 +790,6 @@ function activateNaturalOrder(members, input, lastMessage, allowSelfResponses, i
|
||||
return memberIds;
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function deleteGroup(id) {
|
||||
const response = await fetch("/deletegroup", {
|
||||
method: "POST",
|
||||
@ -800,10 +805,7 @@ async function deleteGroup(id) {
|
||||
printMessages();
|
||||
await getCharacters();
|
||||
|
||||
$("#rm_info_avatar").html("");
|
||||
$("#rm_info_block").transition({ opacity: 0, duration: 0 });
|
||||
select_rm_info("group_delete", id);
|
||||
$("#rm_info_block").transition({ opacity: 1.0, duration: 2000 });
|
||||
|
||||
$("#rm_button_selected_ch").children("h2").text('');
|
||||
setRightTabSelectedClass();
|
||||
@ -823,7 +825,7 @@ export async function editGroup(id, immediately, reload = true) {
|
||||
return await _save(group, reload);
|
||||
}
|
||||
|
||||
saveGroupDebounced(group);
|
||||
saveGroupDebounced(group, reload);
|
||||
}
|
||||
|
||||
let groupAutoModeAbortController = null;
|
||||
@ -849,104 +851,237 @@ async function groupChatAutoModeWorker() {
|
||||
|
||||
async function modifyGroupMember(chat_id, groupMember, isDelete) {
|
||||
const id = groupMember.data("id");
|
||||
|
||||
const template = groupMember.clone();
|
||||
let _thisGroup = groups.find((x) => x.id == chat_id);
|
||||
template.data("id", id);
|
||||
const thisGroup = groups.find((x) => x.id == chat_id);
|
||||
const membersArray = thisGroup?.members ?? newGroupMembers;
|
||||
|
||||
if (isDelete) {
|
||||
$("#rm_group_add_members").prepend(template);
|
||||
} else {
|
||||
$("#rm_group_members").prepend(template);
|
||||
}
|
||||
|
||||
if (_thisGroup) {
|
||||
if (isDelete) {
|
||||
const index = _thisGroup.members.findIndex((x) => x === id);
|
||||
if (index !== -1) {
|
||||
_thisGroup.members.splice(index, 1);
|
||||
}
|
||||
} else {
|
||||
_thisGroup.members.push(id);
|
||||
template.css({ 'order': _thisGroup.members.length });
|
||||
const index = membersArray.findIndex((x) => x === id);
|
||||
if (index !== -1) {
|
||||
membersArray.splice(membersArray.indexOf(id), 1);
|
||||
}
|
||||
await editGroup(selected_group);
|
||||
updateGroupAvatar(_thisGroup);
|
||||
}
|
||||
else {
|
||||
template.css({ 'order': 'unset' });
|
||||
} else {
|
||||
membersArray.unshift(id);
|
||||
}
|
||||
|
||||
groupMember.remove();
|
||||
const groupHasMembers = !!$("#rm_group_members").children().length;
|
||||
if (openGroupId) {
|
||||
await editGroup(openGroupId, false, false);
|
||||
updateGroupAvatar(thisGroup);
|
||||
}
|
||||
|
||||
printGroupCandidates();
|
||||
printGroupMembers();
|
||||
|
||||
const groupHasMembers = getGroupCharacters({ doFilter: false, onlyMembers: true }).length > 0;
|
||||
$("#rm_group_submit").prop("disabled", !groupHasMembers);
|
||||
}
|
||||
|
||||
async function reorderGroupMember(chat_id, groupMember, direction) {
|
||||
const id = groupMember.data("id");
|
||||
const group = groups.find((x) => x.id == chat_id);
|
||||
const thisGroup = groups.find((x) => x.id == chat_id);
|
||||
const memberArray = thisGroup?.members ?? newGroupMembers;
|
||||
|
||||
const indexOf = memberArray.indexOf(id);
|
||||
if (direction == 'down') {
|
||||
const next = memberArray[indexOf + 1];
|
||||
if (next) {
|
||||
memberArray[indexOf + 1] = memberArray[indexOf];
|
||||
memberArray[indexOf] = next;
|
||||
}
|
||||
}
|
||||
if (direction == 'up') {
|
||||
const prev = memberArray[indexOf - 1];
|
||||
if (prev) {
|
||||
memberArray[indexOf - 1] = memberArray[indexOf];
|
||||
memberArray[indexOf] = prev;
|
||||
}
|
||||
}
|
||||
|
||||
printGroupMembers();
|
||||
|
||||
// Existing groups need to modify members list
|
||||
if (group && group.members.length > 1) {
|
||||
const indexOf = group.members.indexOf(id);
|
||||
if (direction == 'down') {
|
||||
const next = group.members[indexOf + 1];
|
||||
if (next) {
|
||||
group.members[indexOf + 1] = group.members[indexOf];
|
||||
group.members[indexOf] = next;
|
||||
}
|
||||
}
|
||||
if (direction == 'up') {
|
||||
const prev = group.members[indexOf - 1];
|
||||
if (prev) {
|
||||
group.members[indexOf - 1] = group.members[indexOf];
|
||||
group.members[indexOf] = prev;
|
||||
}
|
||||
}
|
||||
|
||||
await editGroup(chat_id);
|
||||
updateGroupAvatar(group);
|
||||
// stupid but lifts the manual reordering
|
||||
select_group_chats(chat_id, true);
|
||||
if (openGroupId) {
|
||||
await editGroup(chat_id, false, false);
|
||||
updateGroupAvatar(thisGroup);
|
||||
}
|
||||
// New groups just can't be DOM-ordered
|
||||
else {
|
||||
if (direction == 'down') {
|
||||
groupMember.insertAfter(groupMember.next());
|
||||
}
|
||||
if (direction == 'up') {
|
||||
groupMember.insertBefore(groupMember.prev());
|
||||
}
|
||||
}
|
||||
|
||||
async function onGroupActivationStrategyInput(e) {
|
||||
if (openGroupId) {
|
||||
let _thisGroup = groups.find((x) => x.id == openGroupId);
|
||||
_thisGroup.activation_strategy = Number(e.target.value);
|
||||
await editGroup(openGroupId, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
async function onGroupNameInput() {
|
||||
if (openGroupId) {
|
||||
let _thisGroup = groups.find((x) => x.id == openGroupId);
|
||||
_thisGroup.name = $(this).val();
|
||||
$("#rm_button_selected_ch").children("h2").text(_thisGroup.name);
|
||||
await editGroup(openGroupId);
|
||||
}
|
||||
}
|
||||
|
||||
function isGroupMember(group, avatarId) {
|
||||
if (group && Array.isArray(group.members)) {
|
||||
return group.members.includes(avatarId);
|
||||
} else {
|
||||
return newGroupMembers.includes(avatarId);
|
||||
}
|
||||
}
|
||||
|
||||
function getGroupCharacters({ doFilter, onlyMembers } = {}) {
|
||||
function sortMembersFn(a, b) {
|
||||
const membersArray = thisGroup?.members ?? newGroupMembers;
|
||||
const aIndex = membersArray.indexOf(a.item.avatar);
|
||||
const bIndex = membersArray.indexOf(b.item.avatar);
|
||||
return aIndex - bIndex;
|
||||
}
|
||||
|
||||
const thisGroup = openGroupId && groups.find((x) => x.id == openGroupId);
|
||||
let candidates = characters
|
||||
.filter((x) => isGroupMember(thisGroup, x.avatar) == onlyMembers)
|
||||
.map((x, index) => ({ item: x, id: index, type: 'character' }));
|
||||
|
||||
if (onlyMembers) {
|
||||
candidates.sort(sortMembersFn);
|
||||
} else {
|
||||
sortEntitiesList(candidates);
|
||||
}
|
||||
|
||||
if (doFilter) {
|
||||
candidates = groupCandidatesFilter.applyFilters(candidates);
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
function printGroupCandidates() {
|
||||
const storageKey = 'GroupCandidates_PerPage';
|
||||
$("#rm_group_add_members_pagination").pagination({
|
||||
dataSource: getGroupCharacters({ doFilter: true, onlyMembers: false }),
|
||||
pageSize: 5,
|
||||
pageRange: 1,
|
||||
position: 'top',
|
||||
showPageNumbers: false,
|
||||
showSizeChanger: false,
|
||||
prevText: '<',
|
||||
nextText: '>',
|
||||
showNavigator: true,
|
||||
showSizeChanger: true,
|
||||
pageSize: Number(localStorage.getItem(storageKey)) || 5,
|
||||
sizeChangerOptions: [5, 10, 25, 50, 100, 200],
|
||||
afterSizeSelectorChange: function (e) {
|
||||
localStorage.setItem(storageKey, e.target.value);
|
||||
},
|
||||
callback: function (data) {
|
||||
$("#rm_group_add_members").empty();
|
||||
for (const i of data) {
|
||||
$("#rm_group_add_members").append(getGroupCharacterBlock(i.item));
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function printGroupMembers() {
|
||||
const storageKey = 'GroupMembers_PerPage';
|
||||
$("#rm_group_members_pagination").pagination({
|
||||
dataSource: getGroupCharacters({ doFilter: false, onlyMembers: true }),
|
||||
pageSize: 5,
|
||||
pageRange: 1,
|
||||
position: 'top',
|
||||
showPageNumbers: false,
|
||||
showSizeChanger: false,
|
||||
prevText: '<',
|
||||
nextText: '>',
|
||||
showNavigator: true,
|
||||
showSizeChanger: true,
|
||||
pageSize: Number(localStorage.getItem(storageKey)) || 5,
|
||||
sizeChangerOptions: [5, 10, 25, 50, 100, 200],
|
||||
afterSizeSelectorChange: function (e) {
|
||||
localStorage.setItem(storageKey, e.target.value);
|
||||
},
|
||||
callback: function (data) {
|
||||
$("#rm_group_members").empty();
|
||||
for (const i of data) {
|
||||
$("#rm_group_members").append(getGroupCharacterBlock(i.item));
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function getGroupCharacterBlock(character) {
|
||||
const avatar = getThumbnailUrl('avatar', character.avatar);
|
||||
const template = $("#group_member_template .group_member").clone();
|
||||
const isFav = character.fav || character.fav == 'true';
|
||||
template.data("id", character.avatar);
|
||||
template.find(".avatar img").attr({ "src": avatar, "title": character.avatar });
|
||||
template.find(".ch_name").text(character.name);
|
||||
template.attr("chid", characters.indexOf(character));
|
||||
template.find('.ch_fav').val(isFav);
|
||||
template.toggleClass('is_fav', isFav);
|
||||
template.toggleClass('disabled', isGroupMemberDisabled(character.avatar));
|
||||
|
||||
// Display inline tags
|
||||
const tags = getTagsList(character.avatar);
|
||||
const tagsElement = template.find('.tags');
|
||||
tags.forEach(tag => appendTagToList(tagsElement, tag, {}));
|
||||
|
||||
if (!openGroupId) {
|
||||
template.find('[data-action="speak"]').hide();
|
||||
template.find('[data-action="enable"]').hide();
|
||||
template.find('[data-action="disable"]').hide();
|
||||
}
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
function isGroupMemberDisabled(avatarId) {
|
||||
const thisGroup = openGroupId && groups.find((x) => x.id == openGroupId);
|
||||
return Boolean(thisGroup && thisGroup.disabled_members.includes(avatarId));
|
||||
}
|
||||
|
||||
function onDeleteGroupClick() {
|
||||
if (is_group_generating) {
|
||||
toastr.warning('Not so fast! Wait for the characters to stop typing before deleting the group.');
|
||||
return;
|
||||
}
|
||||
|
||||
$("#dialogue_popup").data("group_id", openGroupId);
|
||||
callPopup('<h3>Delete the group?</h3><p>This will also delete all your chats with that group. If you want to delete a single conversation, select a "View past chats" option in the lower left menu.</p>', "del_group");
|
||||
}
|
||||
|
||||
async function onFavoriteGroupClick() {
|
||||
updateFavButtonState(!fav_grp_checked);
|
||||
if (openGroupId) {
|
||||
let _thisGroup = groups.find((x) => x.id == openGroupId);
|
||||
_thisGroup.fav = fav_grp_checked;
|
||||
await editGroup(openGroupId, false, false);
|
||||
favsToHotswap();
|
||||
}
|
||||
}
|
||||
|
||||
async function onGroupSelfResponsesClick() {
|
||||
if (openGroupId) {
|
||||
let _thisGroup = groups.find((x) => x.id == openGroupId);
|
||||
const value = $(this).prop("checked");
|
||||
_thisGroup.allow_self_responses = value;
|
||||
await editGroup(openGroupId, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
function select_group_chats(groupId, skipAnimation) {
|
||||
const group = groupId && groups.find((x) => x.id == groupId);
|
||||
openGroupId = groupId;
|
||||
newGroupMembers = [];
|
||||
const group = openGroupId && groups.find((x) => x.id == openGroupId);
|
||||
const groupName = group?.name ?? "";
|
||||
const replyStrategy = Number(group?.activation_strategy ?? group_activation_strategy.NATURAL);
|
||||
|
||||
setMenuType(!!group ? 'group_edit' : 'group_create');
|
||||
$("#group_avatar_preview").empty().append(getGroupAvatar(group));
|
||||
$("#rm_group_restore_avatar").toggle(!!group && isDataURL(group.avatar_url));
|
||||
$("#rm_group_chat_name").val(groupName);
|
||||
$("#rm_group_chat_name").off();
|
||||
$("#rm_group_chat_name").on("input", async function () {
|
||||
if (groupId) {
|
||||
let _thisGroup = groups.find((x) => x.id == groupId);
|
||||
_thisGroup.name = $(this).val();
|
||||
$("#rm_button_selected_ch").children("h2").text(_thisGroup.name);
|
||||
await editGroup(groupId);
|
||||
}
|
||||
});
|
||||
$("#rm_group_filter").val("").trigger("input");
|
||||
|
||||
$('input[name="rm_group_activation_strategy"]').off();
|
||||
$('input[name="rm_group_activation_strategy"]').on("input", async function (e) {
|
||||
if (groupId) {
|
||||
let _thisGroup = groups.find((x) => x.id == groupId);
|
||||
_thisGroup.activation_strategy = Number(e.target.value);
|
||||
await editGroup(groupId);
|
||||
}
|
||||
});
|
||||
const replyStrategy = Number(group?.activation_strategy ?? group_activation_strategy.NATURAL);
|
||||
$(`input[name="rm_group_activation_strategy"][value="${replyStrategy}"]`).prop('checked', true);
|
||||
|
||||
if (!skipAnimation) {
|
||||
@ -954,53 +1089,15 @@ function select_group_chats(groupId, skipAnimation) {
|
||||
}
|
||||
|
||||
// render characters list
|
||||
$("#rm_group_add_members").empty();
|
||||
$("#rm_group_members").empty();
|
||||
for (let character of characters) {
|
||||
const avatar =
|
||||
character.avatar != "none"
|
||||
? getThumbnailUrl('avatar', character.avatar)
|
||||
: default_avatar;
|
||||
const template = $("#group_member_template .group_member").clone();
|
||||
const isFav = character.fav || character.fav == 'true';
|
||||
template.data("id", character.avatar);
|
||||
template.find(".avatar img").attr("src", avatar);
|
||||
template.find(".avatar img").attr("title", character.avatar);
|
||||
template.find(".ch_name").text(character.name);
|
||||
template.attr("chid", characters.indexOf(character));
|
||||
template.find('.ch_fav').val(isFav);
|
||||
template.toggleClass('is_fav', isFav);
|
||||
|
||||
// Display inline tags
|
||||
const tags = getTagsList(character.avatar);
|
||||
const tagsElement = template.find('.tags');
|
||||
tags.forEach(tag => appendTagToList(tagsElement, tag, {}));
|
||||
|
||||
if (!group) {
|
||||
template.find('[data-action="speak"]').hide();
|
||||
}
|
||||
|
||||
if (
|
||||
group &&
|
||||
Array.isArray(group.members) &&
|
||||
group.members.includes(character.avatar)
|
||||
) {
|
||||
template.css({ 'order': group.members.indexOf(character.avatar) });
|
||||
template.toggleClass('disabled', group.disabled_members.includes(character.avatar));
|
||||
$("#rm_group_members").append(template);
|
||||
} else {
|
||||
$("#rm_group_add_members").append(template);
|
||||
}
|
||||
}
|
||||
|
||||
sortGroupMembers("#rm_group_add_members .group_member");
|
||||
printGroupCandidates();
|
||||
printGroupMembers();
|
||||
|
||||
const groupHasMembers = !!$("#rm_group_members").children().length;
|
||||
$("#rm_group_submit").prop("disabled", !groupHasMembers);
|
||||
$("#rm_group_allow_self_responses").prop("checked", group && group.allow_self_responses);
|
||||
|
||||
// bottom buttons
|
||||
if (groupId) {
|
||||
if (openGroupId) {
|
||||
$("#rm_group_submit").hide();
|
||||
$("#rm_group_delete").show();
|
||||
$("#rm_group_scenario").show();
|
||||
@ -1013,39 +1110,8 @@ function select_group_chats(groupId, skipAnimation) {
|
||||
$("#rm_group_scenario").hide();
|
||||
}
|
||||
|
||||
$("#rm_group_delete").off();
|
||||
$("#rm_group_delete").on("click", function () {
|
||||
if (is_group_generating) {
|
||||
toastr.warning('Not so fast! Wait for the characters to stop typing before deleting the group.');
|
||||
return;
|
||||
}
|
||||
|
||||
$("#dialogue_popup").data("group_id", groupId);
|
||||
callPopup('<h3>Delete the group?</h3><p>This will also delete all your chats with that group. If you want to delete a single conversation, select a "View past chats" option in the lower left menu.</p>', "del_group");
|
||||
});
|
||||
|
||||
updateFavButtonState(group?.fav ?? false);
|
||||
|
||||
$("#group_favorite_button").off('click');
|
||||
$("#group_favorite_button").on('click', async function () {
|
||||
updateFavButtonState(!fav_grp_checked);
|
||||
if (group) {
|
||||
let _thisGroup = groups.find((x) => x.id == groupId);
|
||||
_thisGroup.fav = fav_grp_checked;
|
||||
await editGroup(groupId);
|
||||
}
|
||||
});
|
||||
|
||||
$("#rm_group_allow_self_responses").off();
|
||||
$("#rm_group_allow_self_responses").on("input", async function () {
|
||||
if (group) {
|
||||
let _thisGroup = groups.find((x) => x.id == groupId);
|
||||
const value = $(this).prop("checked");
|
||||
_thisGroup.allow_self_responses = value;
|
||||
await editGroup(groupId);
|
||||
}
|
||||
});
|
||||
|
||||
// top bar
|
||||
if (group) {
|
||||
$("#rm_group_automode_label").show();
|
||||
@ -1056,118 +1122,112 @@ function select_group_chats(groupId, skipAnimation) {
|
||||
$("#rm_group_automode_label").hide();
|
||||
}
|
||||
|
||||
$("#group_avatar_button").off('input').on("input", uploadGroupAvatar);
|
||||
$("#rm_group_restore_avatar").off('click').on("click", restoreGroupAvatar);
|
||||
eventSource.emit('groupSelected', {detail: {id: openGroupId, group: group}});
|
||||
}
|
||||
|
||||
async function uploadGroupAvatar(event) {
|
||||
const file = event.target.files[0];
|
||||
|
||||
async function uploadGroupAvatar(event) {
|
||||
const file = event.target.files[0];
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const e = await new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = resolve;
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
$('#dialogue_popup').addClass('large_dialogue_popup wide_dialogue_popup');
|
||||
|
||||
const croppedImage = await callPopup(getCropPopup(e.target.result), 'avatarToCrop');
|
||||
|
||||
if (!croppedImage) {
|
||||
return;
|
||||
}
|
||||
|
||||
const thumbnail = await createThumbnail(croppedImage, 96, 144);
|
||||
|
||||
if (!groupId) {
|
||||
$('#group_avatar_preview img').attr('src', thumbnail);
|
||||
$('#rm_group_restore_avatar').show();
|
||||
return;
|
||||
}
|
||||
|
||||
let _thisGroup = groups.find((x) => x.id == groupId);
|
||||
_thisGroup.avatar_url = thumbnail;
|
||||
$("#group_avatar_preview").empty().append(getGroupAvatar(_thisGroup));
|
||||
$("#rm_group_restore_avatar").show();
|
||||
await editGroup(groupId, true, true);
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
async function restoreGroupAvatar() {
|
||||
const confirm = await callPopup('<h3>Are you sure you want to restore the group avatar?</h3> Your custom image will be deleted, and a collage will be used instead.', 'confirm');
|
||||
|
||||
if (!confirm) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!groupId) {
|
||||
$("#group_avatar_preview img").attr("src", default_avatar);
|
||||
$("#rm_group_restore_avatar").hide();
|
||||
return;
|
||||
}
|
||||
|
||||
let _thisGroup = groups.find((x) => x.id == groupId);
|
||||
_thisGroup.avatar_url = '';
|
||||
$("#group_avatar_preview").empty().append(getGroupAvatar(_thisGroup));
|
||||
$("#rm_group_restore_avatar").hide();
|
||||
await editGroup(groupId, true, true);
|
||||
}
|
||||
|
||||
$(document).off("click", ".group_member .right_menu_button");
|
||||
$(document).on("click", ".group_member .right_menu_button", async function (event) {
|
||||
event.stopPropagation();
|
||||
const action = $(this).data('action');
|
||||
const member = $(this).closest('.group_member');
|
||||
|
||||
if (action === 'remove') {
|
||||
await modifyGroupMember(groupId, member, true);
|
||||
}
|
||||
|
||||
if (action === 'add') {
|
||||
await modifyGroupMember(groupId, member, false);
|
||||
}
|
||||
|
||||
if (action === 'enable') {
|
||||
member.removeClass('disabled');
|
||||
const _thisGroup = groups.find(x => x.id === groupId);
|
||||
const index = _thisGroup.disabled_members.indexOf(member.data('id'));
|
||||
if (index !== -1) {
|
||||
_thisGroup.disabled_members.splice(index, 1);
|
||||
}
|
||||
await editGroup(groupId);
|
||||
}
|
||||
|
||||
if (action === 'disable') {
|
||||
member.addClass('disabled');
|
||||
const _thisGroup = groups.find(x => x.id === groupId);
|
||||
_thisGroup.disabled_members.push(member.data('id'));
|
||||
await editGroup(groupId);
|
||||
}
|
||||
|
||||
if (action === 'up' || action === 'down') {
|
||||
await reorderGroupMember(groupId, member, action);
|
||||
}
|
||||
|
||||
if (action === 'view') {
|
||||
openCharacterDefinition(member);
|
||||
}
|
||||
|
||||
if (action === 'speak') {
|
||||
const chid = Number(member.attr('chid'));
|
||||
if (Number.isInteger(chid)) {
|
||||
Generate('normal', { force_chid: chid });
|
||||
}
|
||||
}
|
||||
|
||||
sortGroupMembers("#rm_group_add_members .group_member");
|
||||
await eventSource.emit(event_types.GROUP_UPDATED);
|
||||
const e = await new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = resolve;
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
eventSource.emit('groupSelected', {detail: {id: groupId, group: group}});
|
||||
$('#dialogue_popup').addClass('large_dialogue_popup wide_dialogue_popup');
|
||||
|
||||
const croppedImage = await callPopup(getCropPopup(e.target.result), 'avatarToCrop');
|
||||
|
||||
if (!croppedImage) {
|
||||
return;
|
||||
}
|
||||
|
||||
const thumbnail = await createThumbnail(croppedImage, 96, 144);
|
||||
|
||||
if (!openGroupId) {
|
||||
$('#group_avatar_preview img').attr('src', thumbnail);
|
||||
$('#rm_group_restore_avatar').show();
|
||||
return;
|
||||
}
|
||||
|
||||
let _thisGroup = groups.find((x) => x.id == openGroupId);
|
||||
_thisGroup.avatar_url = thumbnail;
|
||||
$("#group_avatar_preview").empty().append(getGroupAvatar(_thisGroup));
|
||||
$("#rm_group_restore_avatar").show();
|
||||
await editGroup(openGroupId, true, true);
|
||||
}
|
||||
|
||||
async function restoreGroupAvatar() {
|
||||
const confirm = await callPopup('<h3>Are you sure you want to restore the group avatar?</h3> Your custom image will be deleted, and a collage will be used instead.', 'confirm');
|
||||
|
||||
if (!confirm) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!openGroupId) {
|
||||
$("#group_avatar_preview img").attr("src", default_avatar);
|
||||
$("#rm_group_restore_avatar").hide();
|
||||
return;
|
||||
}
|
||||
|
||||
let _thisGroup = groups.find((x) => x.id == openGroupId);
|
||||
_thisGroup.avatar_url = '';
|
||||
$("#group_avatar_preview").empty().append(getGroupAvatar(_thisGroup));
|
||||
$("#rm_group_restore_avatar").hide();
|
||||
await editGroup(openGroupId, true, true);
|
||||
}
|
||||
|
||||
async function onGroupActionClick(event) {
|
||||
event.stopPropagation();
|
||||
const action = $(this).data('action');
|
||||
const member = $(this).closest('.group_member');
|
||||
|
||||
if (action === 'remove') {
|
||||
await modifyGroupMember(openGroupId, member, true);
|
||||
}
|
||||
|
||||
if (action === 'add') {
|
||||
await modifyGroupMember(openGroupId, member, false);
|
||||
}
|
||||
|
||||
if (action === 'enable') {
|
||||
member.removeClass('disabled');
|
||||
const _thisGroup = groups.find(x => x.id === openGroupId);
|
||||
const index = _thisGroup.disabled_members.indexOf(member.data('id'));
|
||||
if (index !== -1) {
|
||||
_thisGroup.disabled_members.splice(index, 1);
|
||||
}
|
||||
await editGroup(openGroupId, false, false);
|
||||
}
|
||||
|
||||
if (action === 'disable') {
|
||||
member.addClass('disabled');
|
||||
const _thisGroup = groups.find(x => x.id === openGroupId);
|
||||
_thisGroup.disabled_members.push(member.data('id'));
|
||||
await editGroup(openGroupId, false, false);
|
||||
}
|
||||
|
||||
if (action === 'up' || action === 'down') {
|
||||
await reorderGroupMember(openGroupId, member, action);
|
||||
}
|
||||
|
||||
if (action === 'view') {
|
||||
openCharacterDefinition(member);
|
||||
}
|
||||
|
||||
if (action === 'speak') {
|
||||
const chid = Number(member.attr('chid'));
|
||||
if (Number.isInteger(chid)) {
|
||||
Generate('normal', { force_chid: chid });
|
||||
}
|
||||
}
|
||||
|
||||
await eventSource.emit(event_types.GROUP_UPDATED);
|
||||
}
|
||||
|
||||
function updateFavButtonState(state) {
|
||||
@ -1177,9 +1237,7 @@ function updateFavButtonState(state) {
|
||||
$("#group_favorite_button").toggleClass('fav_off', !fav_grp_checked);
|
||||
}
|
||||
|
||||
async function selectGroup() {
|
||||
const groupId = $(this).data("id");
|
||||
|
||||
export async function openGroupById(groupId) {
|
||||
if (!is_send_press && !is_group_generating) {
|
||||
if (selected_group !== groupId) {
|
||||
cancelTtsPlay();
|
||||
@ -1219,26 +1277,15 @@ function openCharacterDefinition(characterSelect) {
|
||||
}
|
||||
|
||||
function filterGroupMembers() {
|
||||
const searchValue = $(this).val().trim().toLowerCase();
|
||||
|
||||
if (!searchValue) {
|
||||
$("#rm_group_add_members .group_member").removeClass('hiddenBySearch');
|
||||
} else {
|
||||
$("#rm_group_add_members .group_member").each(function () {
|
||||
const isValidSearch = $(this).find(".ch_name").text().toLowerCase().includes(searchValue);
|
||||
$(this).toggleClass('hiddenBySearch', !isValidSearch);
|
||||
});
|
||||
}
|
||||
const searchValue = $(this).val().toLowerCase();
|
||||
groupCandidatesFilter.setFilterData(FILTER_TYPES.SEARCH, searchValue);
|
||||
}
|
||||
|
||||
async function createGroup() {
|
||||
let name = $("#rm_group_chat_name").val();
|
||||
let allow_self_responses = !!$("#rm_group_allow_self_responses").prop("checked");
|
||||
let activation_strategy = $('input[name="rm_group_activation_strategy"]:checked').val() ?? group_activation_strategy.NATURAL;
|
||||
const members = $("#rm_group_members .group_member")
|
||||
.map((_, x) => $(x).data("id"))
|
||||
.toArray();
|
||||
|
||||
const members = newGroupMembers;
|
||||
const memberNames = characters.filter(x => members.includes(x.avatar)).map(x => x.name).join(", ");
|
||||
|
||||
if (!name) {
|
||||
@ -1268,6 +1315,7 @@ async function createGroup() {
|
||||
});
|
||||
|
||||
if (createGroupResponse.ok) {
|
||||
newGroupMembers = [];
|
||||
const data = await createGroupResponse.json();
|
||||
createTagMapFromList("#groupTagList", data.id);
|
||||
await getCharacters();
|
||||
@ -1351,9 +1399,8 @@ export async function openGroupChat(groupId, chatId) {
|
||||
group['date_last_chat'] = Date.now();
|
||||
updateChatMetadata(group.chat_metadata, true);
|
||||
|
||||
await editGroup(groupId, true);
|
||||
await editGroup(groupId, true, false);
|
||||
await getGroupChat(groupId);
|
||||
sortCharactersList();
|
||||
}
|
||||
|
||||
export async function renameGroupChat(groupId, oldChatId, newChatId) {
|
||||
@ -1445,7 +1492,7 @@ export async function saveGroupBookmarkChat(groupId, name, metadata, mesId) {
|
||||
? chat.slice(0, parseInt(mesId) + 1)
|
||||
: chat;
|
||||
|
||||
await editGroup(groupId, true);
|
||||
await editGroup(groupId, true, false);
|
||||
|
||||
await fetch("/savegroupchat", {
|
||||
method: "POST",
|
||||
@ -1502,7 +1549,10 @@ function doCurMemberListPopout() {
|
||||
}
|
||||
|
||||
jQuery(() => {
|
||||
$(document).on("click", ".group_select", selectGroup);
|
||||
$(document).on("click", ".group_select", function () {
|
||||
const groupId = $(this).data("id");
|
||||
openGroupById(groupId);
|
||||
});
|
||||
$("#rm_group_filter").on("input", filterGroupMembers);
|
||||
$("#rm_group_submit").on("click", createGroup);
|
||||
$("#rm_group_scenario").on("click", setScenarioOverride);
|
||||
@ -1513,4 +1563,12 @@ jQuery(() => {
|
||||
});
|
||||
$("#send_textarea").on("keyup", onSendTextareaInput);
|
||||
$("#groupCurrentMemberPopoutButton").on('click', doCurMemberListPopout);
|
||||
$("#rm_group_chat_name").on("input", onGroupNameInput)
|
||||
$("#rm_group_delete").off().on("click", onDeleteGroupClick);
|
||||
$("#group_favorite_button").on('click', onFavoriteGroupClick);
|
||||
$("#rm_group_allow_self_responses").on("input", onGroupSelfResponsesClick);
|
||||
$('input[name="rm_group_activation_strategy"]').on("input", onGroupActivationStrategyInput);
|
||||
$("#group_avatar_button").on("input", uploadGroupAvatar);
|
||||
$("#rm_group_restore_avatar").on("click", restoreGroupAvatar);
|
||||
$(document).on("click", ".group_member .right_menu_button", onGroupActionClick);
|
||||
});
|
||||
|
@ -45,9 +45,10 @@ import {
|
||||
} from "./secrets.js";
|
||||
|
||||
import {
|
||||
deepClone,
|
||||
delay,
|
||||
download,
|
||||
getFileText,
|
||||
getFileText, getSortableDelay,
|
||||
getStringHash,
|
||||
parseJsonFile,
|
||||
stringFormat,
|
||||
@ -113,9 +114,13 @@ const scale_max = 7900; // Probably more. Save some for the system prompt define
|
||||
const claude_max = 8000; // We have a proper tokenizer, so theoretically could be larger (up to 9k)
|
||||
const palm2_max = 7500; // The real context window is 8192, spare some for padding due to using turbo tokenizer
|
||||
const claude_100k_max = 99000;
|
||||
let ai21_max = 9200; //can easily fit 9k gpt tokens because j2's tokenizer is efficient af
|
||||
const unlocked_max = 100 * 1024;
|
||||
const oai_max_temp = 2.0;
|
||||
const claude_max_temp = 1.0;
|
||||
const claude_max_temp = 1.0; //same as j2
|
||||
const j2_max_topk = 10.0;
|
||||
const j2_max_freq = 5.0;
|
||||
const j2_max_pres = 5.0;
|
||||
const openrouter_website_model = 'OR_Website';
|
||||
|
||||
let biasCache = undefined;
|
||||
@ -161,13 +166,26 @@ export const chat_completion_sources = {
|
||||
CLAUDE: 'claude',
|
||||
SCALE: 'scale',
|
||||
OPENROUTER: 'openrouter',
|
||||
AI21: 'ai21',
|
||||
};
|
||||
|
||||
const prefixMap = selected_group ? {
|
||||
assistant: "",
|
||||
user: "",
|
||||
system: "OOC: "
|
||||
}
|
||||
: {
|
||||
assistant: "{{char}}:",
|
||||
user: "{{user}}:",
|
||||
system: ""
|
||||
};
|
||||
|
||||
const default_settings = {
|
||||
preset_settings_openai: 'Default',
|
||||
temp_openai: 0.9,
|
||||
freq_pen_openai: 0.7,
|
||||
pres_pen_openai: 0.7,
|
||||
count_pen: 0.0,
|
||||
top_p_openai: 1.0,
|
||||
top_k_openai: 0,
|
||||
stream_openai: false,
|
||||
@ -188,6 +206,7 @@ const default_settings = {
|
||||
wi_format: default_wi_format,
|
||||
openai_model: 'gpt-3.5-turbo',
|
||||
claude_model: 'claude-instant-v1',
|
||||
ai21_model: 'j2-ultra',
|
||||
windowai_model: '',
|
||||
openrouter_model: openrouter_website_model,
|
||||
jailbreak_system: false,
|
||||
@ -199,6 +218,7 @@ const default_settings = {
|
||||
show_external_models: false,
|
||||
proxy_password: '',
|
||||
assistant_prefill: '',
|
||||
use_ai21_tokenizer: false,
|
||||
};
|
||||
|
||||
const oai_settings = {
|
||||
@ -206,6 +226,7 @@ const oai_settings = {
|
||||
temp_openai: 1.0,
|
||||
freq_pen_openai: 0,
|
||||
pres_pen_openai: 0,
|
||||
count_pen: 0.0,
|
||||
top_p_openai: 1.0,
|
||||
top_k_openai: 0,
|
||||
stream_openai: false,
|
||||
@ -226,6 +247,7 @@ const oai_settings = {
|
||||
wi_format: default_wi_format,
|
||||
openai_model: 'gpt-3.5-turbo',
|
||||
claude_model: 'claude-instant-v1',
|
||||
ai21_model: 'j2-ultra',
|
||||
windowai_model: '',
|
||||
openrouter_model: openrouter_website_model,
|
||||
jailbreak_system: false,
|
||||
@ -237,6 +259,7 @@ const oai_settings = {
|
||||
show_external_models: false,
|
||||
proxy_password: '',
|
||||
assistant_prefill: '',
|
||||
use_ai21_tokenizer: false,
|
||||
};
|
||||
|
||||
let openai_setting_names;
|
||||
@ -341,17 +364,22 @@ function setupChatCompletionPromptManager(openAiSettings) {
|
||||
containerIdentifier: 'completion_prompt_manager',
|
||||
listIdentifier: 'completion_prompt_manager_list',
|
||||
toggleDisabled: ['main'],
|
||||
draggable: true,
|
||||
sortableDelay: getSortableDelay(),
|
||||
defaultPrompts: {
|
||||
main: default_main_prompt,
|
||||
nsfw: default_nsfw_prompt,
|
||||
jailbreak: default_jailbreak_prompt,
|
||||
enhanceDefinitions: default_enhance_definitions_prompt
|
||||
}
|
||||
},
|
||||
promptOrder: {
|
||||
strategy: 'global',
|
||||
dummyId: 100000
|
||||
},
|
||||
};
|
||||
|
||||
promptManager.saveServiceSettings = () => {
|
||||
return saveSettings();
|
||||
saveSettingsDebounced();
|
||||
return new Promise((resolve) => eventSource.once(event_types.SETTINGS_UPDATED, resolve));
|
||||
}
|
||||
|
||||
promptManager.tryGenerate = () => {
|
||||
@ -671,7 +699,7 @@ function populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, ty
|
||||
*
|
||||
* @returns {Object} prompts - The prepared and merged system and user-defined prompts.
|
||||
*/
|
||||
function preparePromptsForChatCompletion(Scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts) {
|
||||
function preparePromptsForChatCompletion(Scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts, systemPromptOverride, jailbreakPromptOverride) {
|
||||
const scenarioText = Scenario ? `[Circumstances and context of the dialogue: ${Scenario}]` : '';
|
||||
const charPersonalityText = charPersonality ? `[${name2}'s personality: ${charPersonality}]` : '';
|
||||
|
||||
@ -724,7 +752,6 @@ function preparePromptsForChatCompletion(Scenario, charPersonality, name2, world
|
||||
});
|
||||
|
||||
// Apply character-specific main prompt
|
||||
const systemPromptOverride = promptManager.activeCharacter.data?.system_prompt ?? null;
|
||||
const systemPrompt = prompts.get('main') ?? null;
|
||||
if (systemPromptOverride && systemPrompt) {
|
||||
const mainOriginalContent = systemPrompt.content;
|
||||
@ -734,7 +761,6 @@ function preparePromptsForChatCompletion(Scenario, charPersonality, name2, world
|
||||
}
|
||||
|
||||
// Apply character-specific jailbreak
|
||||
const jailbreakPromptOverride = promptManager.activeCharacter.data?.post_history_instructions ?? null;
|
||||
const jailbreakPrompt = prompts.get('jailbreak') ?? null;
|
||||
if (jailbreakPromptOverride && jailbreakPrompt) {
|
||||
const jbOriginalContent = jailbreakPrompt.content;
|
||||
@ -778,7 +804,9 @@ function prepareOpenAIMessages({
|
||||
type,
|
||||
quietPrompt,
|
||||
extensionPrompts,
|
||||
cyclePrompt
|
||||
cyclePrompt,
|
||||
systemPromptOverride,
|
||||
jailbreakPromptOverride,
|
||||
} = {}, dryRun) {
|
||||
// Without a character selected, there is no way to accurately calculate tokens
|
||||
if (!promptManager.activeCharacter && dryRun) return [null, false];
|
||||
@ -791,7 +819,7 @@ function prepareOpenAIMessages({
|
||||
|
||||
try {
|
||||
// Merge markers and ordered user prompts with system prompts
|
||||
const prompts = preparePromptsForChatCompletion(Scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts);
|
||||
const prompts = preparePromptsForChatCompletion(Scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts, systemPromptOverride, jailbreakPromptOverride);
|
||||
|
||||
// Fill the chat completion with as much context as the budget allows
|
||||
populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, type, cyclePrompt });
|
||||
@ -806,8 +834,10 @@ function prepareOpenAIMessages({
|
||||
promptManager.error = 'The name of at least one character contained whitespaces or special characters. Please check your user and character name.';
|
||||
} else {
|
||||
toastr.error('An unknown error occurred while counting tokens. Further information may be available in console.')
|
||||
chatCompletion.log('Unexpected error:');
|
||||
chatCompletion.log('----- Unexpected error while preparing prompts -----');
|
||||
chatCompletion.log(error);
|
||||
chatCompletion.log(error.stack);
|
||||
chatCompletion.log('----------------------------------------------------');
|
||||
}
|
||||
} finally {
|
||||
// Pass chat completion to prompt manager for inspection
|
||||
@ -966,6 +996,8 @@ function getChatCompletionModel() {
|
||||
return '';
|
||||
case chat_completion_sources.OPENROUTER:
|
||||
return oai_settings.openrouter_model !== openrouter_website_model ? oai_settings.openrouter_model : null;
|
||||
case chat_completion_sources.AI21:
|
||||
return oai_settings.ai21_model;
|
||||
default:
|
||||
throw new Error(`Unknown chat completion source: ${oai_settings.chat_completion_source}`);
|
||||
}
|
||||
@ -1047,9 +1079,18 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
||||
const isClaude = oai_settings.chat_completion_source == chat_completion_sources.CLAUDE;
|
||||
const isOpenRouter = oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER;
|
||||
const isScale = oai_settings.chat_completion_source == chat_completion_sources.SCALE;
|
||||
const isAI21 = oai_settings.chat_completion_source == chat_completion_sources.AI21;
|
||||
const isTextCompletion = oai_settings.chat_completion_source == chat_completion_sources.OPENAI && (oai_settings.openai_model.startsWith('text-') || oai_settings.openai_model.startsWith('code-'));
|
||||
const stream = type !== 'quiet' && oai_settings.stream_openai && !isScale;
|
||||
const isQuiet = type === 'quiet';
|
||||
const stream = oai_settings.stream_openai && !isQuiet && !isScale && !isAI21;
|
||||
|
||||
if (isAI21) {
|
||||
const joinedMsgs = openai_msgs_tosend.reduce((acc, obj) => {
|
||||
const prefix = prefixMap[obj.role];
|
||||
return acc + (prefix ? (selected_group ? "\n" : prefix + " ") : "") + obj.content + "\n";
|
||||
}, "");
|
||||
openai_msgs_tosend = substituteParams(joinedMsgs);
|
||||
}
|
||||
|
||||
// If we're using the window.ai extension, use that instead
|
||||
// Doesn't support logit bias yet
|
||||
@ -1106,6 +1147,13 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
||||
generate_data['api_url_scale'] = oai_settings.api_url_scale;
|
||||
}
|
||||
|
||||
if (isAI21) {
|
||||
generate_data['use_ai21'] = true;
|
||||
generate_data['top_k'] = parseFloat(oai_settings.top_k_openai);
|
||||
generate_data['count_pen'] = parseFloat(oai_settings.count_pen);
|
||||
generate_data['stop_tokens'] = [name1 + ':', 'prompt: [Start a new chat]'];
|
||||
}
|
||||
|
||||
const generate_url = '/generate_openai';
|
||||
const response = await fetch(generate_url, {
|
||||
method: 'POST',
|
||||
@ -1296,6 +1344,7 @@ class TokenHandler {
|
||||
}
|
||||
|
||||
function countTokens(messages, full = false) {
|
||||
let shouldTokenizeAI21 = oai_settings.chat_completion_source === chat_completion_sources.AI21 && oai_settings.use_ai21_tokenizer;
|
||||
let chatId = 'undefined';
|
||||
|
||||
try {
|
||||
@ -1328,12 +1377,12 @@ function countTokens(messages, full = false) {
|
||||
if (typeof cachedCount === 'number') {
|
||||
token_count += cachedCount;
|
||||
}
|
||||
else {
|
||||
|
||||
else {
|
||||
jQuery.ajax({
|
||||
async: false,
|
||||
type: 'POST', //
|
||||
url: `/tokenize_openai?model=${model}`,
|
||||
url: shouldTokenizeAI21 ? '/tokenize_ai21' : `/tokenize_openai?model=${model}`,
|
||||
data: JSON.stringify([message]),
|
||||
dataType: "json",
|
||||
contentType: "application/json",
|
||||
@ -1849,6 +1898,7 @@ function loadOpenAISettings(data, settings) {
|
||||
oai_settings.temp_openai = settings.temp_openai ?? default_settings.temp_openai;
|
||||
oai_settings.freq_pen_openai = settings.freq_pen_openai ?? default_settings.freq_pen_openai;
|
||||
oai_settings.pres_pen_openai = settings.pres_pen_openai ?? default_settings.pres_pen_openai;
|
||||
oai_settings.count_pen = settings.count_pen ?? default_settings.count_pen;
|
||||
oai_settings.top_p_openai = settings.top_p_openai ?? default_settings.top_p_openai;
|
||||
oai_settings.top_k_openai = settings.top_k_openai ?? default_settings.top_k_openai;
|
||||
oai_settings.stream_openai = settings.stream_openai ?? default_settings.stream_openai;
|
||||
@ -1864,6 +1914,7 @@ function loadOpenAISettings(data, settings) {
|
||||
oai_settings.claude_model = settings.claude_model ?? default_settings.claude_model;
|
||||
oai_settings.windowai_model = settings.windowai_model ?? default_settings.windowai_model;
|
||||
oai_settings.openrouter_model = settings.openrouter_model ?? default_settings.openrouter_model;
|
||||
oai_settings.ai21_model = settings.ai21_model ?? default_settings.ai21_model;
|
||||
oai_settings.chat_completion_source = settings.chat_completion_source ?? default_settings.chat_completion_source;
|
||||
oai_settings.api_url_scale = settings.api_url_scale ?? default_settings.api_url_scale;
|
||||
oai_settings.show_external_models = settings.show_external_models ?? default_settings.show_external_models;
|
||||
@ -1882,7 +1933,7 @@ function loadOpenAISettings(data, settings) {
|
||||
if (settings.wrap_in_quotes !== undefined) oai_settings.wrap_in_quotes = !!settings.wrap_in_quotes;
|
||||
if (settings.names_in_completion !== undefined) oai_settings.names_in_completion = !!settings.names_in_completion;
|
||||
if (settings.openai_model !== undefined) oai_settings.openai_model = settings.openai_model;
|
||||
|
||||
if (settings.use_ai21_tokenizer !== undefined) oai_settings.use_ai21_tokenizer = !!settings.use_ai21_tokenizer;
|
||||
$('#stream_toggle').prop('checked', oai_settings.stream_openai);
|
||||
$('#api_url_scale').val(oai_settings.api_url_scale);
|
||||
$('#openai_proxy_password').val(oai_settings.proxy_password);
|
||||
@ -1894,6 +1945,8 @@ function loadOpenAISettings(data, settings) {
|
||||
$(`#model_claude_select option[value="${oai_settings.claude_model}"`).attr('selected', true);
|
||||
$('#model_windowai_select').val(oai_settings.windowai_model);
|
||||
$(`#model_windowai_select option[value="${oai_settings.windowai_model}"`).attr('selected', true);
|
||||
$('#model_ai21_select').val(oai_settings.ai21_model);
|
||||
$(`#model_ai21_select option[value="${oai_settings.ai21_model}"`).attr('selected', true);
|
||||
$('#openai_max_context').val(oai_settings.openai_max_context);
|
||||
$('#openai_max_context_counter').text(`${oai_settings.openai_max_context}`);
|
||||
$('#model_openrouter_select').val(oai_settings.openrouter_model);
|
||||
@ -1909,7 +1962,7 @@ function loadOpenAISettings(data, settings) {
|
||||
$('#legacy_streaming').prop('checked', oai_settings.legacy_streaming);
|
||||
$('#openai_show_external_models').prop('checked', oai_settings.show_external_models);
|
||||
$('#openai_external_category').toggle(oai_settings.show_external_models);
|
||||
|
||||
$('#use_ai21_tokenizer').prop('checked', oai_settings.use_ai21_tokenizer);
|
||||
if (settings.impersonation_prompt !== undefined) oai_settings.impersonation_prompt = settings.impersonation_prompt;
|
||||
|
||||
$('#impersonation_prompt_textarea').val(oai_settings.impersonation_prompt);
|
||||
@ -1932,6 +1985,9 @@ function loadOpenAISettings(data, settings) {
|
||||
$('#pres_pen_openai').val(oai_settings.pres_pen_openai);
|
||||
$('#pres_pen_counter_openai').text(Number(oai_settings.pres_pen_openai).toFixed(2));
|
||||
|
||||
$('#count_pen').val(oai_settings.count_pen);
|
||||
$('#count_pen_counter').text(Number(oai_settings.count_pen).toFixed(2));
|
||||
|
||||
$('#top_p_openai').val(oai_settings.top_p_openai);
|
||||
$('#top_p_counter_openai').text(Number(oai_settings.top_p_openai).toFixed(2));
|
||||
|
||||
@ -1974,7 +2030,7 @@ async function getStatusOpen() {
|
||||
return resultCheckStatusOpen();
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.SCALE || oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.SCALE || oai_settings.chat_completion_source == chat_completion_sources.CLAUDE || oai_settings.chat_completion_source == chat_completion_sources.AI21) {
|
||||
let status = 'Unable to verify key; press "Test Message" to validate.';
|
||||
setOnlineStatus(status);
|
||||
return resultCheckStatusOpen();
|
||||
@ -2071,9 +2127,11 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
|
||||
claude_model: settings.claude_model,
|
||||
windowai_model: settings.windowai_model,
|
||||
openrouter_model: settings.openrouter_model,
|
||||
ai21_model: settings.ai21_model,
|
||||
temperature: settings.temp_openai,
|
||||
frequency_penalty: settings.freq_pen_openai,
|
||||
presence_penalty: settings.pres_pen_openai,
|
||||
count_penalty: settings.count_pen,
|
||||
top_p: settings.top_p_openai,
|
||||
top_k: settings.top_k_openai,
|
||||
openai_max_context: settings.openai_max_context,
|
||||
@ -2101,6 +2159,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
|
||||
api_url_scale: settings.api_url_scale,
|
||||
show_external_models: settings.show_external_models,
|
||||
assistant_prefill: settings.assistant_prefill,
|
||||
use_ai21_tokenizer: settings.use_ai21_tokenizer,
|
||||
};
|
||||
|
||||
const savePresetSettings = await fetch(`/savepreset_openai?name=${name}`, {
|
||||
@ -2286,7 +2345,11 @@ async function onExportPresetClick() {
|
||||
return;
|
||||
}
|
||||
|
||||
const preset = openai_settings[openai_setting_names[oai_settings.preset_settings_openai]];
|
||||
const preset = deepClone(openai_settings[openai_setting_names[oai_settings.preset_settings_openai]]);
|
||||
|
||||
delete preset.reverse_proxy;
|
||||
delete preset.proxy_password;
|
||||
|
||||
const presetJsonString = JSON.stringify(preset, null, 4);
|
||||
download(presetJsonString, oai_settings.preset_settings_openai, 'application/json');
|
||||
}
|
||||
@ -2400,6 +2463,7 @@ function onSettingsPresetChange() {
|
||||
temperature: ['#temp_openai', 'temp_openai', false],
|
||||
frequency_penalty: ['#freq_pen_openai', 'freq_pen_openai', false],
|
||||
presence_penalty: ['#pres_pen_openai', 'pres_pen_openai', false],
|
||||
count_penalty: ['#count_pen', 'count_pen', false],
|
||||
top_p: ['#top_p_openai', 'top_p_openai', false],
|
||||
top_k: ['#top_k_openai', 'top_k_openai', false],
|
||||
max_context_unlocked: ['#oai_max_context_unlocked', 'max_context_unlocked', true],
|
||||
@ -2407,6 +2471,7 @@ function onSettingsPresetChange() {
|
||||
claude_model: ['#model_claude_select', 'claude_model', false],
|
||||
windowai_model: ['#model_windowai_select', 'windowai_model', false],
|
||||
openrouter_model: ['#model_openrouter_select', 'openrouter_model', false],
|
||||
ai21_model: ['#model_ai21_select', 'ai21_model', false],
|
||||
openai_max_context: ['#openai_max_context', 'openai_max_context', false],
|
||||
openai_max_tokens: ['#openai_max_tokens', 'openai_max_tokens', false],
|
||||
wrap_in_quotes: ['#wrap_in_quotes', 'wrap_in_quotes', true],
|
||||
@ -2430,6 +2495,7 @@ function onSettingsPresetChange() {
|
||||
show_external_models: ['#openai_show_external_models', 'show_external_models', true],
|
||||
proxy_password: ['#openai_proxy_password', 'proxy_password', false],
|
||||
assistant_prefill: ['#claude_assistant_prefill', 'assistant_prefill', false],
|
||||
use_ai21_tokenizer: ['#use_ai21_tokenizer', 'use_ai21_tokenizer', false],
|
||||
};
|
||||
|
||||
const presetName = $('#settings_perset_openai').find(":selected").text();
|
||||
@ -2554,6 +2620,11 @@ async function onModelChange() {
|
||||
oai_settings.openrouter_model = value;
|
||||
}
|
||||
|
||||
if ($(this).is('#model_ai21_select')) {
|
||||
console.log('AI21 model changed to', value);
|
||||
oai_settings.ai21_model = value;
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) {
|
||||
if (oai_settings.max_context_unlocked) {
|
||||
$('#openai_max_context').attr('max', unlocked_max);
|
||||
@ -2640,6 +2711,38 @@ async function onModelChange() {
|
||||
$('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.AI21) {
|
||||
if (oai_settings.max_context_unlocked) {
|
||||
$('#openai_max_context').attr('max', unlocked_max);
|
||||
} else {
|
||||
$('#openai_max_context').attr('max', ai21_max);
|
||||
}
|
||||
|
||||
oai_settings.openai_max_context = Math.min(oai_settings.openai_max_context, Number($('#openai_max_context').attr('max')));
|
||||
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
|
||||
|
||||
oai_settings.temp_openai = Math.min(claude_max_temp, oai_settings.temp_openai);
|
||||
$('#temp_openai').attr('max', claude_max_temp).val(oai_settings.temp_openai).trigger('input');
|
||||
|
||||
oai_settings.freq_pen_openai = Math.min(j2_max_freq, oai_settings.freq_pen_openai < 0 ? 0 : oai_settings.freq_pen_openai);
|
||||
$('#freq_pen_openai').attr('min', 0).attr('max', j2_max_freq).val(oai_settings.freq_pen_openai).trigger('input');
|
||||
|
||||
oai_settings.pres_pen_openai = Math.min(j2_max_pres, oai_settings.pres_pen_openai < 0 ? 0 : oai_settings.pres_pen_openai);
|
||||
$('#pres_pen_openai').attr('min', 0).attr('max', j2_max_pres).val(oai_settings.pres_pen_openai).trigger('input');
|
||||
|
||||
oai_settings.top_k_openai = Math.min(j2_max_topk, oai_settings.top_k_openai);
|
||||
$('#top_k_openai').attr('max', j2_max_topk).val(oai_settings.top_k_openai).trigger('input');
|
||||
} else if (oai_settings.chat_completion_source != chat_completion_sources.AI21) {
|
||||
oai_settings.freq_pen_openai = Math.min(2.0, oai_settings.freq_pen_openai);
|
||||
$('#freq_pen_openai').attr('min', -2.0).attr('max', 2.0).val(oai_settings.freq_pen_openai).trigger('input');
|
||||
|
||||
oai_settings.freq_pen_openai = Math.min(2.0, oai_settings.pres_pen_openai);
|
||||
$('#pres_pen_openai').attr('min', -2.0).attr('max', 2.0).val(oai_settings.freq_pen_openai).trigger('input');
|
||||
|
||||
oai_settings.top_k_openai = Math.min(200, oai_settings.top_k_openai);
|
||||
$('#top_k_openai').attr('max', 200).val(oai_settings.top_k_openai).trigger('input');
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
eventSource.emit(event_types.CHATCOMPLETION_MODEL_CHANGED, value);
|
||||
}
|
||||
@ -2730,6 +2833,19 @@ async function onConnectButtonClick(e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.AI21) {
|
||||
const api_key_ai21 = $('#api_key_ai21').val().trim();
|
||||
|
||||
if (api_key_ai21.length) {
|
||||
await writeSecret(SECRET_KEYS.AI21, api_key_ai21);
|
||||
}
|
||||
|
||||
if (!secret_state[SECRET_KEYS.AI21]) {
|
||||
console.log('No secret key saved for AI21');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$("#api_loading_openai").css("display", 'inline-block');
|
||||
$("#api_button_openai").css("display", 'none');
|
||||
saveSettingsDebounced();
|
||||
@ -2759,7 +2875,9 @@ function toggleChatCompletionForms() {
|
||||
else if (oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER) {
|
||||
$('#model_openrouter_select').trigger('change');
|
||||
}
|
||||
|
||||
else if (oai_settings.chat_completion_source == chat_completion_sources.AI21) {
|
||||
$('#model_ai21_select').trigger('change');
|
||||
}
|
||||
$('[data-source]').each(function () {
|
||||
const validSources = $(this).data('source').split(',');
|
||||
$(this).toggle(validSources.includes(oai_settings.chat_completion_source));
|
||||
@ -2817,7 +2935,12 @@ $(document).ready(async function () {
|
||||
oai_settings.pres_pen_openai = $(this).val();
|
||||
$('#pres_pen_counter_openai').text(Number($(this).val()).toFixed(2));
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$(document).on('input', '#count_pen', function () {
|
||||
oai_settings.count_pen = $(this).val();
|
||||
$('#count_pen_counter').text(Number($(this).val()).toFixed(2));
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$(document).on('input', '#top_p_openai', function () {
|
||||
@ -2855,6 +2978,14 @@ $(document).ready(async function () {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#use_ai21_tokenizer').on('change', function () {
|
||||
oai_settings.use_ai21_tokenizer = !!$('#use_ai21_tokenizer').prop('checked');
|
||||
oai_settings.use_ai21_tokenizer ? ai21_max = 8191 : ai21_max = 9200;
|
||||
oai_settings.openai_max_context = Math.min(ai21_max, oai_settings.openai_max_context);
|
||||
$('#openai_max_context').attr('max', ai21_max).val(oai_settings.openai_max_context).trigger('input');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#names_in_completion').on('change', function () {
|
||||
oai_settings.names_in_completion = !!$('#names_in_completion').prop('checked');
|
||||
saveSettingsDebounced();
|
||||
@ -3022,6 +3153,7 @@ $(document).ready(async function () {
|
||||
$("#model_windowai_select").on("change", onModelChange);
|
||||
$("#model_scale_select").on("change", onModelChange);
|
||||
$("#model_openrouter_select").on("change", onModelChange);
|
||||
$("#model_ai21_select").on("change", onModelChange);
|
||||
$("#settings_perset_openai").on("change", onSettingsPresetChange);
|
||||
$("#new_oai_preset").on("click", onNewPresetClick);
|
||||
$("#delete_oai_preset").on("click", onDeletePresetClick);
|
||||
|
@ -8,7 +8,6 @@ import {
|
||||
reloadCurrentChat,
|
||||
getRequestHeaders,
|
||||
substituteParams,
|
||||
updateVisibleDivs,
|
||||
eventSource,
|
||||
event_types,
|
||||
getCurrentChatId,
|
||||
@ -18,7 +17,7 @@ import {
|
||||
setCharacterId,
|
||||
setEditedMessageId
|
||||
} from "../script.js";
|
||||
import { favsToHotswap, isMobile, initMovingUI } from "./RossAscends-mods.js";
|
||||
import { isMobile, initMovingUI } from "./RossAscends-mods.js";
|
||||
import {
|
||||
groups,
|
||||
resetSelectedGroup,
|
||||
@ -34,8 +33,7 @@ export {
|
||||
loadMovingUIState,
|
||||
collapseNewlines,
|
||||
playMessageSound,
|
||||
sortGroupMembers,
|
||||
sortCharactersList,
|
||||
sortEntitiesList,
|
||||
fixMarkdown,
|
||||
power_user,
|
||||
pygmalion_options,
|
||||
@ -46,9 +44,7 @@ export {
|
||||
export const MAX_CONTEXT_DEFAULT = 4096;
|
||||
const MAX_CONTEXT_UNLOCKED = 65536;
|
||||
|
||||
const defaultStoryString = `{{#if description}}{{description}}{{/if}}
|
||||
{{#if personality}}{{personality}}{{/if}}
|
||||
{{#if scenario}}Scenario: {{scenario}}{{/if}}`;
|
||||
const defaultStoryString = "{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}{{/if}}";
|
||||
const defaultExampleSeparator = '***';
|
||||
const defaultChatStart = '***';
|
||||
|
||||
@ -167,6 +163,7 @@ let power_user = {
|
||||
trim_spaces: true,
|
||||
relaxed_api_urls: false,
|
||||
|
||||
default_instruct: '',
|
||||
instruct: {
|
||||
enabled: false,
|
||||
wrap: true,
|
||||
@ -181,6 +178,7 @@ let power_user = {
|
||||
separator_sequence: '',
|
||||
macro: false,
|
||||
names_force_groups: true,
|
||||
activation_regex: '',
|
||||
},
|
||||
|
||||
context: {
|
||||
@ -803,7 +801,6 @@ function loadPowerUserSettings(settings, data) {
|
||||
|
||||
|
||||
$(`#character_sort_order option[data-order="${power_user.sort_order}"][data-field="${power_user.sort_field}"]`).prop("selected", true);
|
||||
sortCharactersList();
|
||||
reloadMarkdownProcessor(power_user.render_formulas);
|
||||
loadInstructMode();
|
||||
loadContextSettings();
|
||||
@ -812,11 +809,10 @@ function loadPowerUserSettings(settings, data) {
|
||||
switchSpoilerMode();
|
||||
loadMovingUIState();
|
||||
loadCharListState();
|
||||
|
||||
}
|
||||
|
||||
async function loadCharListState() {
|
||||
if (document.getElementById('CharID0') !== null) {
|
||||
if (document.querySelector('.character_select') !== null) {
|
||||
console.debug('setting charlist state to...')
|
||||
if (power_user.charListGrid === true) {
|
||||
console.debug('..to grid')
|
||||
@ -944,6 +940,7 @@ function loadInstructMode() {
|
||||
{ id: "instruct_macro", property: "macro", isCheckbox: true },
|
||||
{ id: "instruct_names_force_groups", property: "names_force_groups", isCheckbox: true },
|
||||
{ id: "instruct_last_output_sequence", property: "last_output_sequence", isCheckbox: false },
|
||||
{ id: "instruct_activation_regex", property: "activation_regex", isCheckbox: false },
|
||||
];
|
||||
|
||||
if (power_user.instruct.names_force_groups === undefined) {
|
||||
@ -974,6 +971,19 @@ function loadInstructMode() {
|
||||
$('#instruct_presets').append(option);
|
||||
});
|
||||
|
||||
function highlightDefaultPreset() {
|
||||
$('#instruct_set_default').toggleClass('default', power_user.default_instruct === power_user.instruct.preset);
|
||||
}
|
||||
|
||||
$('#instruct_set_default').on('click', function () {
|
||||
power_user.default_instruct = power_user.instruct.preset;
|
||||
$(this).addClass('default');
|
||||
toastr.success(`Default instruct preset set to ${power_user.default_instruct}`);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
highlightDefaultPreset();
|
||||
|
||||
$('#instruct_presets').on('change', function () {
|
||||
const name = $(this).find(':selected').val();
|
||||
const preset = instruct_presets.find(x => x.name === name);
|
||||
@ -995,6 +1005,8 @@ function loadInstructMode() {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
highlightDefaultPreset();
|
||||
});
|
||||
}
|
||||
|
||||
@ -1070,8 +1082,8 @@ export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvata
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
const separatorSequence = power_user.instruct.separator_sequence && !isUser
|
||||
? power_user.instruct.separator_sequence
|
||||
: (power_user.instruct.wrap ? '\n' : '');
|
||||
const textArray = includeNames ? [sequence, `${name}: ${mes}`, separatorSequence] : [sequence, mes, separatorSequence];
|
||||
: separator;
|
||||
const textArray = includeNames ? [sequence, `${name}: ${mes}` + separatorSequence] : [sequence, mes + separatorSequence];
|
||||
const text = textArray.filter(x => x).join(separator);
|
||||
return text;
|
||||
}
|
||||
@ -1103,7 +1115,7 @@ export function formatInstructModePrompt(name, isImpersonate, promptBias, name1,
|
||||
text += (includeNames ? promptBias : (separator + promptBias));
|
||||
}
|
||||
|
||||
return text.trimEnd();
|
||||
return text.trimEnd() + (includeNames ? '' : separator);
|
||||
}
|
||||
|
||||
const sortFunc = (a, b) => power_user.sort_order == 'asc' ? compareFunc(a, b) : compareFunc(b, a);
|
||||
@ -1129,46 +1141,13 @@ const compareFunc = (first, second) => {
|
||||
}
|
||||
};
|
||||
|
||||
function sortCharactersList() {
|
||||
const arr1 = groups.map(x => ({
|
||||
item: x,
|
||||
id: x.id,
|
||||
selector: '.group_select',
|
||||
attribute: 'grid',
|
||||
}))
|
||||
const arr2 = characters.map((x, index) => ({
|
||||
item: x,
|
||||
id: index,
|
||||
selector: '.character_select',
|
||||
attribute: 'chid',
|
||||
}));
|
||||
|
||||
const array = [...arr1, ...arr2];
|
||||
|
||||
if (power_user.sort_field == undefined || array.length === 0) {
|
||||
function sortEntitiesList(entities) {
|
||||
if (power_user.sort_field == undefined || entities.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let orderedList = array.slice().sort((a, b) => sortFunc(a.item, b.item));
|
||||
|
||||
for (const item of array) {
|
||||
$(`${item.selector}[${item.attribute}="${item.id}"]`).css({ 'order': orderedList.indexOf(item) });
|
||||
}
|
||||
updateVisibleDivs('#rm_print_characters_block', true);
|
||||
entities.sort((a, b) => sortFunc(a.item, b.item));
|
||||
}
|
||||
|
||||
function sortGroupMembers(selector) {
|
||||
if (power_user.sort_field == undefined || characters.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let orderedList = characters.slice().sort(sortFunc);
|
||||
|
||||
for (let i = 0; i < characters.length; i++) {
|
||||
$(`${selector}[chid="${i}"]`).css({ 'order': orderedList.indexOf(characters[i]) });
|
||||
}
|
||||
}
|
||||
|
||||
async function saveTheme() {
|
||||
const name = await callPopup('Enter a theme preset name:', 'input');
|
||||
|
||||
@ -1900,6 +1879,7 @@ $(document).ready(() => {
|
||||
power_user.never_resize_avatars = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#show_card_avatar_urls").on('input', function () {
|
||||
power_user.show_card_avatar_urls = !!$(this).prop('checked');
|
||||
printCharacters();
|
||||
@ -1925,8 +1905,7 @@ $(document).ready(() => {
|
||||
power_user.sort_field = $(this).find(":selected").data('field');
|
||||
power_user.sort_order = $(this).find(":selected").data('order');
|
||||
power_user.sort_rule = $(this).find(":selected").data('rule');
|
||||
sortCharactersList();
|
||||
favsToHotswap();
|
||||
printCharacters();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
@ -2038,12 +2017,6 @@ $(document).ready(() => {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
/* $("#removeXML").on("input", function () {
|
||||
power_user.removeXML = !!$(this).prop('checked');
|
||||
reloadCurrentChat();
|
||||
saveSettingsDebounced();
|
||||
}); */
|
||||
|
||||
$("#token_padding").on("input", function () {
|
||||
power_user.token_padding = Number($(this).val());
|
||||
saveSettingsDebounced();
|
||||
|
@ -8,6 +8,7 @@ export const SECRET_KEYS = {
|
||||
CLAUDE: 'api_key_claude',
|
||||
OPENROUTER: 'api_key_openrouter',
|
||||
SCALE: 'api_key_scale',
|
||||
AI21: 'api_key_ai21',
|
||||
}
|
||||
|
||||
const INPUT_MAP = {
|
||||
@ -18,6 +19,7 @@ const INPUT_MAP = {
|
||||
[SECRET_KEYS.CLAUDE]: '#api_key_claude',
|
||||
[SECRET_KEYS.OPENROUTER]: '#api_key_openrouter',
|
||||
[SECRET_KEYS.SCALE]: '#api_key_scale',
|
||||
[SECRET_KEYS.AI21]: '#api_key_ai21',
|
||||
}
|
||||
|
||||
async function clearSecret() {
|
||||
|
@ -4,12 +4,12 @@ import {
|
||||
this_chid,
|
||||
callPopup,
|
||||
menu_type,
|
||||
updateVisibleDivs,
|
||||
getCharacters,
|
||||
updateCharacterCount,
|
||||
entitiesFilter,
|
||||
} from "../script.js";
|
||||
import { FILTER_TYPES } from "./filters.js";
|
||||
|
||||
import { selected_group } from "./group-chats.js";
|
||||
import { groupCandidatesFilter, selected_group } from "./group-chats.js";
|
||||
import { uuidv4 } from "./utils.js";
|
||||
|
||||
export {
|
||||
@ -17,7 +17,6 @@ export {
|
||||
tag_map,
|
||||
loadTagsSettings,
|
||||
printTagFilters,
|
||||
isElementTagged,
|
||||
getTagsList,
|
||||
appendTagToList,
|
||||
createTagMapFromList,
|
||||
@ -26,18 +25,11 @@ export {
|
||||
};
|
||||
|
||||
const random_id = () => uuidv4();
|
||||
const TAG_LOGIC_AND = true; // switch to false to use OR logic for combining tags
|
||||
const CHARACTER_SELECTOR = '#rm_print_characters_block > div';
|
||||
const GROUP_MEMBER_SELECTOR = '#rm_group_add_members > div';
|
||||
const CHARACTER_FILTER_SELECTOR = '#rm_characters_block .rm_tag_filter';
|
||||
const GROUP_FILTER_SELECTOR = '#rm_group_chats_block .rm_tag_filter';
|
||||
|
||||
function getCharacterSelector(listSelector) {
|
||||
if ($(listSelector).is(GROUP_FILTER_SELECTOR)) {
|
||||
return GROUP_MEMBER_SELECTOR;
|
||||
}
|
||||
|
||||
return CHARACTER_SELECTOR;
|
||||
function getFilterHelper(listSelector) {
|
||||
return $(listSelector).is(GROUP_FILTER_SELECTOR) ? groupCandidatesFilter : entitiesFilter;
|
||||
}
|
||||
|
||||
export const tag_filter_types = {
|
||||
@ -68,37 +60,20 @@ const DEFAULT_TAGS = [
|
||||
let tags = [];
|
||||
let tag_map = {};
|
||||
|
||||
function applyFavFilter(characterSelector) {
|
||||
function applyFavFilter(filterHelper) {
|
||||
const isSelected = $(this).hasClass('selected');
|
||||
const displayFavoritesOnly = !isSelected;
|
||||
|
||||
$(this).toggleClass('selected', displayFavoritesOnly);
|
||||
$(characterSelector).removeClass('hiddenByFav');
|
||||
|
||||
$(characterSelector).each(function () {
|
||||
if (displayFavoritesOnly) {
|
||||
if ($(this).find(".ch_fav").length !== 0) {
|
||||
const shouldBeDisplayed = $(this).find(".ch_fav").val().toLowerCase().includes(true);
|
||||
$(this).toggleClass('hiddenByFav', !shouldBeDisplayed);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
updateCharacterCount(characterSelector);
|
||||
updateVisibleDivs('#rm_print_characters_block', true);
|
||||
filterHelper.setFilterData(FILTER_TYPES.FAV, displayFavoritesOnly);
|
||||
}
|
||||
|
||||
function filterByGroups(characterSelector) {
|
||||
function filterByGroups(filterHelper) {
|
||||
const isSelected = $(this).hasClass('selected');
|
||||
const displayGroupsOnly = !isSelected;
|
||||
$(this).toggleClass('selected', displayGroupsOnly);
|
||||
$(characterSelector).removeClass('hiddenByGroup');
|
||||
|
||||
$(characterSelector).each((_, element) => {
|
||||
$(element).toggleClass('hiddenByGroup', displayGroupsOnly && !$(element).hasClass('group_select'));
|
||||
});
|
||||
updateCharacterCount(characterSelector);
|
||||
updateVisibleDivs('#rm_print_characters_block', true);
|
||||
filterHelper.setFilterData(FILTER_TYPES.GROUP, displayGroupsOnly);
|
||||
}
|
||||
|
||||
function loadTagsSettings(settings) {
|
||||
@ -291,7 +266,6 @@ function appendTagToList(listElement, tag, { removable, selectable, action, isGe
|
||||
return;
|
||||
}
|
||||
|
||||
const characterSelector = getCharacterSelector($(listElement));
|
||||
|
||||
let tagElement = $('#tag_template .tag').clone();
|
||||
tagElement.attr('id', tag.id);
|
||||
@ -311,16 +285,17 @@ function appendTagToList(listElement, tag, { removable, selectable, action, isGe
|
||||
tagElement.find('.tag_name').text('').attr('title', tag.name).addClass(tag.icon);
|
||||
}
|
||||
|
||||
if (tag.excluded) {
|
||||
isGeneralList ? $(tagElement).addClass('excluded') : $(listElement).closest('.character_select, .group_select').addClass('hiddenByTag');
|
||||
if (tag.excluded && isGeneralList) {
|
||||
$(tagElement).addClass('excluded');
|
||||
}
|
||||
|
||||
if (selectable) {
|
||||
tagElement.on('click', () => onTagFilterClick.bind(tagElement)(listElement, characterSelector));
|
||||
tagElement.on('click', () => onTagFilterClick.bind(tagElement)(listElement));
|
||||
}
|
||||
|
||||
if (action) {
|
||||
tagElement.on('click', () => action.bind(tagElement)(characterSelector));
|
||||
const filter = getFilterHelper($(listElement));
|
||||
tagElement.on('click', () => action.bind(tagElement)(filter));
|
||||
tagElement.addClass('actionable');
|
||||
}
|
||||
if (action && tag.id === 2) {
|
||||
@ -330,7 +305,7 @@ function appendTagToList(listElement, tag, { removable, selectable, action, isGe
|
||||
$(listElement).append(tagElement);
|
||||
}
|
||||
|
||||
function onTagFilterClick(listElement, characterSelector) {
|
||||
function onTagFilterClick(listElement) {
|
||||
let excludeTag;
|
||||
if ($(this).hasClass('selected')) {
|
||||
$(this).removeClass('selected');
|
||||
@ -356,44 +331,10 @@ function onTagFilterClick(listElement, characterSelector) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Overhaul this somehow to use settings tag IDs instead
|
||||
const tagIds = [...($(listElement).find(".tag.selected:not(.actionable)").map((_, el) => $(el).attr("id")))];
|
||||
const excludedTagIds = [...($(listElement).find(".tag.excluded:not(.actionable)").map((_, el) => $(el).attr("id")))];
|
||||
$(characterSelector).each((_, element) => applyFilterToElement(tagIds, excludedTagIds, element));
|
||||
updateCharacterCount(characterSelector);
|
||||
updateVisibleDivs('#rm_print_characters_block', true);
|
||||
}
|
||||
|
||||
function applyFilterToElement(tagIds, excludedTagIds, element) {
|
||||
const tagFlags = tagIds.map(tagId => isElementTagged(element, tagId));
|
||||
const trueFlags = tagFlags.filter(x => x);
|
||||
const isTagged = TAG_LOGIC_AND ? tagFlags.length === trueFlags.length : trueFlags.length > 0;
|
||||
|
||||
const excludedTagFlags = excludedTagIds.map(tagId => isElementTagged(element, tagId));
|
||||
const isExcluded = excludedTagFlags.includes(true);
|
||||
|
||||
if (isExcluded) {
|
||||
$(element).addClass('hiddenByTag');
|
||||
} else if (tagIds.length > 0 && !isTagged) {
|
||||
$(element).addClass('hiddenByTag');
|
||||
} else {
|
||||
$(element).removeClass('hiddenByTag');
|
||||
}
|
||||
}
|
||||
|
||||
function isElementTagged(element, tagId) {
|
||||
const isGroup = $(element).hasClass('group_select');
|
||||
const isCharacter = $(element).hasClass('character_select') || $(element).hasClass('group_member');
|
||||
const idAttr = isGroup ? 'grid' : 'chid';
|
||||
const elementId = $(element).attr(idAttr);
|
||||
const lookupValue = isCharacter ? characters[elementId].avatar : elementId;
|
||||
const isTagged = Array.isArray(tag_map[lookupValue]) && tag_map[lookupValue].includes(tagId);
|
||||
return isTagged;
|
||||
}
|
||||
|
||||
function clearTagsFilter(characterSelector) {
|
||||
$('.rm_tag_filter .tag').removeClass('selected');
|
||||
$(characterSelector).removeClass('hiddenByTag');
|
||||
const filterHelper = getFilterHelper($(listElement));
|
||||
filterHelper.setFilterData(FILTER_TYPES.TAG, { excluded: excludedTagIds, selected: tagIds });
|
||||
}
|
||||
|
||||
function printTagFilters(type = tag_filter_types.character) {
|
||||
|
@ -149,9 +149,6 @@ function setWorldInfoSettings(settings, data) {
|
||||
});
|
||||
|
||||
$("#world_editor_select").trigger("change");
|
||||
|
||||
// Update settings
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
// World Info Editor
|
||||
|
164
public/style.css
164
public/style.css
@ -1270,18 +1270,19 @@ input[type="file"] {
|
||||
filter: brightness(150%);
|
||||
}
|
||||
|
||||
#rm_character_count {
|
||||
padding: 5px;
|
||||
font-size: calc(var(--mainFontSize) * .8);
|
||||
font-weight: bold;
|
||||
#rm_print_characters_pagination {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 5px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#rm_print_characters_block {
|
||||
/* padding: 5px 0; */
|
||||
overflow-y: auto;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
/* row-gap: 5px; */
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body.charListGrid #rm_print_characters_block {
|
||||
@ -1350,7 +1351,8 @@ body.charListGrid #rm_print_characters_block .tags_inline {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#floatingPrompt, #cfgConfig {
|
||||
#floatingPrompt,
|
||||
#cfgConfig {
|
||||
overflow-y: auto;
|
||||
max-width: 90svw;
|
||||
max-height: 90svh;
|
||||
@ -1470,6 +1472,14 @@ select option:not(:checked) {
|
||||
color: #4b9c00 !important;
|
||||
}
|
||||
|
||||
#instruct_set_default {
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
#instruct_set_default.default {
|
||||
color: #f44336 !important;
|
||||
}
|
||||
|
||||
.displayBlock {
|
||||
display: block !important;
|
||||
}
|
||||
@ -1501,6 +1511,7 @@ select option:not(:checked) {
|
||||
display: flex;
|
||||
overflow-y: auto;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#rm_characters_block .right_menu_button {
|
||||
@ -1984,35 +1995,6 @@ grammarly-extension {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#rm_info_block {
|
||||
display: none;
|
||||
width: 100%;
|
||||
height: 80%;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
|
||||
}
|
||||
|
||||
#rm_info_panel {
|
||||
font-size: calc(var(--mainFontSize) + .5rem);
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#rm_info_button {
|
||||
width: min-content;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#rm_info_avatar {
|
||||
display: flex;
|
||||
column-gap: 10px;
|
||||
width: fit-content;
|
||||
text-align: center;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
#delete_button,
|
||||
.redWarningBG {
|
||||
background-color: var(--crimson70a) !important;
|
||||
@ -3233,6 +3215,12 @@ h5 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.group_pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#rm_group_chats_block .tag.filterByGroups {
|
||||
display: none;
|
||||
}
|
||||
@ -3299,6 +3287,7 @@ h5 {
|
||||
justify-content: flex-start;
|
||||
max-height: 66%;
|
||||
overflow: hidden;
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
.tag_name {
|
||||
@ -3988,13 +3977,6 @@ a {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hiddenByTag,
|
||||
.hiddenByFav,
|
||||
.hiddenByGroup,
|
||||
.hiddenBySearch {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Message images */
|
||||
.mes .mes_img_container {
|
||||
max-width: 100%;
|
||||
@ -4363,7 +4345,7 @@ input.extension_missing[type="checkbox"] {
|
||||
}
|
||||
|
||||
.fillLeft .scrollableInner {
|
||||
padding: 0.5em 1em 0.5em 0.5em
|
||||
padding: 0.5em 1em 0.5em 0.5em
|
||||
}
|
||||
|
||||
.width100p {
|
||||
@ -4674,6 +4656,7 @@ toolcool-color-picker {
|
||||
color: var(--SmartThemeQuoteColor);
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.openai_restorable .right_menu_button img {
|
||||
@ -5054,6 +5037,10 @@ body.waifuMode .zoomed_avatar {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.mes_text img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#extensions_settings,
|
||||
#extensions_settings2 {
|
||||
width: 100% !important;
|
||||
@ -5205,11 +5192,9 @@ body.waifuMode .zoomed_avatar {
|
||||
}
|
||||
|
||||
.mes_buttons {
|
||||
font-size: calc(var(--mainFontSize)*1.2)
|
||||
font-size: calc(var(--mainFontSize)*1.2);
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
.drag-grabber,
|
||||
.pull-tab {
|
||||
display: none !important;
|
||||
@ -5221,10 +5206,6 @@ body.waifuMode .zoomed_avatar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mes-text {
|
||||
padding-right: 25px;
|
||||
}
|
||||
|
||||
#right-nav-panel,
|
||||
#left-nav-panel,
|
||||
#floatingPrompt,
|
||||
@ -5289,8 +5270,9 @@ body.waifuMode .zoomed_avatar {
|
||||
}
|
||||
|
||||
body.waifuMode .expression-holder {
|
||||
display: inline;
|
||||
/*display: inline;*/
|
||||
|
||||
max-width: 100vw;
|
||||
height: 100vh;
|
||||
width: max-content;
|
||||
margin: 0 auto;
|
||||
@ -5616,3 +5598,81 @@ body.waifuMode .zoomed_avatar {
|
||||
vertical-align: middle;
|
||||
/* To align with adjacent text */
|
||||
}
|
||||
|
||||
.paginationjs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
/* Pagination */
|
||||
.paginationsjs-pages {
|
||||
margin: 0.5em 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.paginationjs-pages ul {
|
||||
list-style-type: none;
|
||||
margin: 0.25em;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.paginationjs-size-changer select {
|
||||
width: unset;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.paginationjs-pages ul li a {
|
||||
padding: 0.05em 0.5em;
|
||||
text-decoration: none;
|
||||
color: var(--SmartThemeBodyColor);
|
||||
border: 1px solid var(--white30a);
|
||||
border-radius: 5px;
|
||||
transition: opacity 0.2s;
|
||||
opacity: 0.8;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.paginationjs-pages ul li a:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.paginationjs-pages ul li.active a {
|
||||
color: var(--SmartThemeQuoteColor);
|
||||
border-color: var(--SmartThemeQuoteColor);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.paginationjs-pages ul li.disabled a {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.paginationjs-nav {
|
||||
padding: 5px;
|
||||
font-size: calc(var(--mainFontSize) * .8);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#select_chat_search {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: white;
|
||||
display: inline-block;
|
||||
/* Change display to inline-block */
|
||||
vertical-align: middle;
|
||||
/* Align to middle if there's a height discrepancy */
|
||||
width: 200px;
|
||||
font-size: 16px;
|
||||
z-index: 10;
|
||||
margin-left: 10px;
|
||||
/* Give some space between the button and search box */
|
||||
}
|
||||
|
188
server.js
188
server.js
@ -288,6 +288,7 @@ function humanizedISO8601DateTime() {
|
||||
var is_colab = process.env.colaburl !== undefined;
|
||||
var charactersPath = 'public/characters/';
|
||||
var chatsPath = 'public/chats/';
|
||||
const UPLOADS_PATH = './uploads';
|
||||
const AVATAR_WIDTH = 400;
|
||||
const AVATAR_HEIGHT = 600;
|
||||
const jsonParser = express.json({ limit: '100mb' });
|
||||
@ -427,7 +428,7 @@ app.use('/characters', (req, res) => {
|
||||
res.send(data);
|
||||
});
|
||||
});
|
||||
app.use(multer({ dest: "uploads", limits: { fieldSize: 10 * 1024 * 1024 } }).single("avatar"));
|
||||
app.use(multer({ dest: UPLOADS_PATH, limits: { fieldSize: 10 * 1024 * 1024 } }).single("avatar"));
|
||||
app.get("/", function (request, response) {
|
||||
response.sendFile(process.cwd() + "/public/index.html");
|
||||
});
|
||||
@ -618,7 +619,7 @@ app.post("/generate_textgenerationwebui", jsonParser, async function (request, r
|
||||
websocket.close();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
let rawMessage = null;
|
||||
try {
|
||||
// This lunacy is because the websocket can fail to connect AFTER we're awaiting 'message'... so 'message' never triggers.
|
||||
@ -632,7 +633,7 @@ app.post("/generate_textgenerationwebui", jsonParser, async function (request, r
|
||||
resolve(data, isBinary);
|
||||
});
|
||||
});
|
||||
} catch(err) {
|
||||
} catch (err) {
|
||||
console.error("Socket error:", err);
|
||||
websocket.close();
|
||||
yield "[SillyTavern] Streaming failed:\n" + err;
|
||||
@ -1005,7 +1006,7 @@ function charaFormatData(data) {
|
||||
return char;
|
||||
}
|
||||
|
||||
app.post("/createcharacter", urlencodedParser, function (request, response) {
|
||||
app.post("/createcharacter", urlencodedParser, async function (request, response) {
|
||||
if (!request.body) return response.sendStatus(400);
|
||||
|
||||
request.body.ch_name = sanitize(request.body.ch_name);
|
||||
@ -1022,8 +1023,9 @@ app.post("/createcharacter", urlencodedParser, function (request, response) {
|
||||
charaWrite(defaultAvatar, char, internalName, response, avatarName);
|
||||
} else {
|
||||
const crop = tryParse(request.query.crop);
|
||||
const uploadPath = path.join("./uploads/", request.file.filename);
|
||||
charaWrite(uploadPath, char, internalName, response, avatarName, crop);
|
||||
const uploadPath = path.join(UPLOADS_PATH, request.file.filename);
|
||||
await charaWrite(uploadPath, char, internalName, response, avatarName, crop);
|
||||
fs.unlinkSync(uploadPath);
|
||||
}
|
||||
});
|
||||
|
||||
@ -1120,9 +1122,10 @@ app.post("/editcharacter", urlencodedParser, async function (request, response)
|
||||
await charaWrite(avatarPath, char, target_img, response, 'Character saved');
|
||||
} else {
|
||||
const crop = tryParse(request.query.crop);
|
||||
const newAvatarPath = path.join("./uploads/", request.file.filename);
|
||||
const newAvatarPath = path.join(UPLOADS_PATH, request.file.filename);
|
||||
invalidateThumbnail('avatar', request.body.avatar_url);
|
||||
await charaWrite(newAvatarPath, char, target_img, response, 'Character saved', crop);
|
||||
fs.unlinkSync(newAvatarPath);
|
||||
}
|
||||
}
|
||||
catch {
|
||||
@ -1373,7 +1376,20 @@ app.post("/getcharacters", jsonParser, function (request, response) {
|
||||
});
|
||||
});
|
||||
|
||||
app.post("/getonecharacter", jsonParser, async function (request, response) {
|
||||
if (!request.body) return response.sendStatus(400);
|
||||
const item = request.body.avatar_url;
|
||||
const filePath = path.join(charactersPath, item);
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return response.sendStatus(404);
|
||||
}
|
||||
|
||||
characters = {};
|
||||
await processCharacter(item, 0);
|
||||
|
||||
return response.send(characters[0]);
|
||||
});
|
||||
|
||||
/**
|
||||
* Handle a POST request to get the stats object
|
||||
@ -1531,13 +1547,14 @@ app.post("/downloadbackground", urlencodedParser, function (request, response) {
|
||||
response_dw_bg = response;
|
||||
if (!request.body || !request.file) return response.sendStatus(400);
|
||||
|
||||
const img_path = path.join("uploads/", request.file.filename);
|
||||
const img_path = path.join(UPLOADS_PATH, request.file.filename);
|
||||
const filename = request.file.originalname;
|
||||
|
||||
try {
|
||||
fs.copyFileSync(img_path, path.join('public/backgrounds/', filename));
|
||||
invalidateThumbnail('bg', filename);
|
||||
response_dw_bg.send(filename);
|
||||
fs.unlinkSync(img_path);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
response_dw_bg.sendStatus(500);
|
||||
@ -2052,13 +2069,15 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
|
||||
|
||||
let png_name = '';
|
||||
let filedata = request.file;
|
||||
let uploadPath = path.join('./uploads', filedata.filename);
|
||||
let uploadPath = path.join(UPLOADS_PATH, filedata.filename);
|
||||
var format = request.body.file_type;
|
||||
const defaultAvatarPath = './public/img/ai4.png';
|
||||
//console.log(format);
|
||||
if (filedata) {
|
||||
if (format == 'json') {
|
||||
fs.readFile(uploadPath, 'utf8', async (err, data) => {
|
||||
fs.unlinkSync(uploadPath);
|
||||
|
||||
if (err) {
|
||||
console.log(err);
|
||||
response.send({ error: true });
|
||||
@ -2139,8 +2158,9 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
|
||||
|
||||
if (format == 'webp') {
|
||||
try {
|
||||
let convertedPath = path.join('./uploads', path.basename(uploadPath, ".webp") + ".png")
|
||||
let convertedPath = path.join(UPLOADS_PATH, path.basename(uploadPath, ".webp") + ".png")
|
||||
await webp.dwebp(uploadPath, convertedPath, "-o");
|
||||
fs.unlinkSync(uploadPath);
|
||||
uploadPath = convertedPath;
|
||||
}
|
||||
catch {
|
||||
@ -2155,7 +2175,8 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
|
||||
unsetFavFlag(jsonData);
|
||||
jsonData = readFromV2(jsonData);
|
||||
let char = JSON.stringify(jsonData);
|
||||
charaWrite(uploadPath, char, png_name, response, { file_name: png_name });
|
||||
await charaWrite(uploadPath, char, png_name, response, { file_name: png_name });
|
||||
fs.unlinkSync(uploadPath);
|
||||
} else if (jsonData.name !== undefined) {
|
||||
console.log('Found a v1 character file.');
|
||||
|
||||
@ -2181,6 +2202,7 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
|
||||
char = convertToV2(char);
|
||||
char = JSON.stringify(char);
|
||||
await charaWrite(uploadPath, char, png_name, response, { file_name: png_name });
|
||||
fs.unlinkSync(uploadPath);
|
||||
} else {
|
||||
console.log('Unknown character card format');
|
||||
response.send({ error: true });
|
||||
@ -2323,9 +2345,9 @@ app.post("/exportcharacter", jsonParser, async function (request, response) {
|
||||
try {
|
||||
let json = await charaRead(filename);
|
||||
let stringByteArray = utf8Encode.encode(json).toString();
|
||||
let inputWebpPath = `./uploads/${Date.now()}_input.webp`;
|
||||
let outputWebpPath = `./uploads/${Date.now()}_output.webp`;
|
||||
let metadataPath = `./uploads/${Date.now()}_metadata.exif`;
|
||||
let inputWebpPath = path.join(UPLOADS_PATH, `${Date.now()}_input.webp`);
|
||||
let outputWebpPath = path.join(UPLOADS_PATH, `${Date.now()}_output.webp`);
|
||||
let metadataPath = path.join(UPLOADS_PATH, `${Date.now()}_metadata.exif`);
|
||||
let metadata =
|
||||
{
|
||||
"Exif": {
|
||||
@ -2360,7 +2382,10 @@ app.post("/importgroupchat", urlencodedParser, function (request, response) {
|
||||
try {
|
||||
const filedata = request.file;
|
||||
const chatname = humanizedISO8601DateTime();
|
||||
fs.copyFileSync(`./uploads/${filedata.filename}`, (`${directories.groupChats}/${chatname}.jsonl`));
|
||||
const pathToUpload = path.join(UPLOADS_PATH, filedata.filename);
|
||||
const pathToNewFile = path.join(directories.groupChats, `${chatname}.jsonl`);
|
||||
fs.copyFileSync(pathToUpload, pathToNewFile);
|
||||
fs.unlinkSync(pathToUpload);
|
||||
return response.send({ res: chatname });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@ -2379,7 +2404,7 @@ app.post("/importchat", urlencodedParser, function (request, response) {
|
||||
|
||||
if (filedata) {
|
||||
if (format === 'json') {
|
||||
fs.readFile(`./uploads/${filedata.filename}`, 'utf8', (err, data) => {
|
||||
fs.readFile(path.join(UPLOADS_PATH, filedata.filename), 'utf8', (err, data) => {
|
||||
|
||||
if (err) {
|
||||
console.log(err);
|
||||
@ -2473,7 +2498,7 @@ app.post("/importchat", urlencodedParser, function (request, response) {
|
||||
}
|
||||
if (format === 'jsonl') {
|
||||
//console.log(humanizedISO8601DateTime()+':imported chat format is JSONL');
|
||||
const fileStream = fs.createReadStream('./uploads/' + filedata.filename);
|
||||
const fileStream = fs.createReadStream(path.join(UPLOADS_PATH, filedata.filename));
|
||||
const rl = readline.createInterface({
|
||||
input: fileStream,
|
||||
crlfDelay: Infinity
|
||||
@ -2483,7 +2508,7 @@ app.post("/importchat", urlencodedParser, function (request, response) {
|
||||
let jsonData = json5.parse(line);
|
||||
|
||||
if (jsonData.user_name !== undefined || jsonData.name !== undefined) {
|
||||
fs.copyFile(`./uploads/${filedata.filename}`, (`${chatsPath + avatar_url}/${ch_name} - ${humanizedISO8601DateTime()}.jsonl`), (err) => {
|
||||
fs.copyFile(path.join(UPLOADS_PATH, filedata.filename), (`${chatsPath + avatar_url}/${ch_name} - ${humanizedISO8601DateTime()}.jsonl`), (err) => {
|
||||
if (err) {
|
||||
response.send({ error: true });
|
||||
return console.log(err);
|
||||
@ -2512,8 +2537,9 @@ app.post('/importworldinfo', urlencodedParser, (request, response) => {
|
||||
if (request.body.convertedData) {
|
||||
fileContents = request.body.convertedData;
|
||||
} else {
|
||||
const pathToUpload = path.join('./uploads/', request.file.filename);
|
||||
const pathToUpload = path.join(UPLOADS_PATH, request.file.filename);
|
||||
fileContents = fs.readFileSync(pathToUpload, 'utf8');
|
||||
fs.unlinkSync(pathToUpload);
|
||||
}
|
||||
|
||||
try {
|
||||
@ -2565,7 +2591,7 @@ app.post('/uploaduseravatar', urlencodedParser, async (request, response) => {
|
||||
if (!request.file) return response.sendStatus(400);
|
||||
|
||||
try {
|
||||
const pathToUpload = path.join('./uploads/' + request.file.filename);
|
||||
const pathToUpload = path.join(UPLOADS_PATH, request.file.filename);
|
||||
const crop = tryParse(request.query.crop);
|
||||
let rawImg = await jimp.read(pathToUpload);
|
||||
|
||||
@ -2633,7 +2659,7 @@ app.post('/creategroup', jsonParser, (request, response) => {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
const id = Date.now();
|
||||
const id = String(Date.now());
|
||||
const groupMetadata = {
|
||||
id: id,
|
||||
name: request.body.name ?? 'New Group',
|
||||
@ -2855,6 +2881,26 @@ function invalidateThumbnail(type, file) {
|
||||
}
|
||||
}
|
||||
|
||||
function cleanUploads() {
|
||||
try {
|
||||
if (fs.existsSync(UPLOADS_PATH)) {
|
||||
const uploads = fs.readdirSync(UPLOADS_PATH);
|
||||
|
||||
if (!uploads.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.debug(`Cleaning uploads folder (${uploads.length} files)`);
|
||||
uploads.forEach(file => {
|
||||
const pathToFile = path.join(UPLOADS_PATH, file);
|
||||
fs.unlinkSync(pathToFile);
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureThumbnailCache() {
|
||||
const cacheFiles = fs.readdirSync(directories.thumbnailsBg);
|
||||
|
||||
@ -3277,6 +3323,10 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
|
||||
return sendScaleRequest(request, response_generate_openai);
|
||||
}
|
||||
|
||||
if (request.body.use_ai21) {
|
||||
return sendAI21Request(request, response_generate_openai);
|
||||
}
|
||||
|
||||
let api_url;
|
||||
let api_key_openai;
|
||||
let headers;
|
||||
@ -3466,6 +3516,94 @@ app.post("/tokenize_openai", jsonParser, function (request, response_tokenize_op
|
||||
response_tokenize_openai.send({ "token_count": num_tokens });
|
||||
});
|
||||
|
||||
async function sendAI21Request(request, response) {
|
||||
if (!request.body) return response.sendStatus(400);
|
||||
const controller = new AbortController();
|
||||
console.log(request.body.messages)
|
||||
request.socket.removeAllListeners('close');
|
||||
request.socket.on('close', function () {
|
||||
controller.abort();
|
||||
});
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
accept: 'application/json',
|
||||
'content-type': 'application/json',
|
||||
Authorization: `Bearer ${readSecret(SECRET_KEYS.AI21)}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
numResults: 1,
|
||||
maxTokens: request.body.max_tokens,
|
||||
minTokens: 0,
|
||||
temperature: request.body.temperature,
|
||||
topP: request.body.top_p,
|
||||
stopSequences: request.body.stop_tokens,
|
||||
topKReturn: request.body.top_k,
|
||||
frequencyPenalty: {
|
||||
scale: request.body.frequency_penalty * 100,
|
||||
applyToWhitespaces: false,
|
||||
applyToPunctuations: false,
|
||||
applyToNumbers: false,
|
||||
applyToStopwords: false,
|
||||
applyToEmojis: false
|
||||
},
|
||||
presencePenalty: {
|
||||
scale: request.body.presence_penalty,
|
||||
applyToWhitespaces: false,
|
||||
applyToPunctuations: false,
|
||||
applyToNumbers: false,
|
||||
applyToStopwords: false,
|
||||
applyToEmojis: false
|
||||
},
|
||||
countPenalty: {
|
||||
scale: request.body.count_pen,
|
||||
applyToWhitespaces: false,
|
||||
applyToPunctuations: false,
|
||||
applyToNumbers: false,
|
||||
applyToStopwords: false,
|
||||
applyToEmojis: false
|
||||
},
|
||||
prompt: request.body.messages
|
||||
}),
|
||||
signal: controller.signal,
|
||||
};
|
||||
|
||||
fetch(`https://api.ai21.com/studio/v1/${request.body.model}/complete`, options)
|
||||
.then(r => r.json())
|
||||
.then(r => {
|
||||
if (r.completions === undefined) {
|
||||
console.log(r)
|
||||
} else {
|
||||
console.log(r.completions[0].data.text)
|
||||
}
|
||||
const reply = { choices: [{ "message": { "content": r.completions[0].data.text, } }] };
|
||||
return response.send(reply)
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
return response.send({ error: true })
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
app.post("/tokenize_ai21", jsonParser, function (request, response_tokenize_ai21 = response) {
|
||||
if (!request.body) return response_tokenize_ai21.sendStatus(400);
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
accept: 'application/json',
|
||||
'content-type': 'application/json',
|
||||
Authorization: `Bearer ${readSecret(SECRET_KEYS.AI21)}`
|
||||
},
|
||||
body: JSON.stringify({ text: request.body[0].content })
|
||||
};
|
||||
|
||||
fetch('https://api.ai21.com/studio/v1/tokenize', options)
|
||||
.then(response => response.json())
|
||||
.then(response => response_tokenize_ai21.send({ "token_count": response.tokens.length }))
|
||||
.catch(err => console.error(err));
|
||||
});
|
||||
|
||||
app.post("/save_preset", jsonParser, function (request, response) {
|
||||
const name = sanitize(request.body.name);
|
||||
if (!request.body.preset || !name) {
|
||||
@ -3623,6 +3761,7 @@ const setupTasks = async function () {
|
||||
ensurePublicDirectoriesExist();
|
||||
await ensureThumbnailCache();
|
||||
contentManager.checkForNewContent();
|
||||
cleanUploads();
|
||||
|
||||
// Colab users could run the embedded tool
|
||||
if (!is_colab) await convertWebp();
|
||||
@ -3649,7 +3788,7 @@ const setupTasks = async function () {
|
||||
console.log('Launching...');
|
||||
|
||||
if (autorun) open(autorunUrl.toString());
|
||||
|
||||
|
||||
console.log('\x1b[32mSillyTavern is listening on: ' + tavernUrl + '\x1b[0m');
|
||||
|
||||
if (listen) {
|
||||
@ -3778,6 +3917,7 @@ const SECRET_KEYS = {
|
||||
DEEPL: 'deepl',
|
||||
OPENROUTER: 'api_key_openrouter',
|
||||
SCALE: 'api_key_scale',
|
||||
AI21: 'api_key_ai21'
|
||||
}
|
||||
|
||||
function migrateSecrets() {
|
||||
@ -4175,7 +4315,7 @@ app.post('/upload_sprite_pack', urlencodedParser, async (request, response) => {
|
||||
return response.sendStatus(404);
|
||||
}
|
||||
|
||||
const spritePackPath = path.join("./uploads/", file.filename);
|
||||
const spritePackPath = path.join(UPLOADS_PATH, file.filename);
|
||||
const sprites = await getImageBuffers(spritePackPath);
|
||||
const files = fs.readdirSync(spritesPath);
|
||||
|
||||
@ -4233,7 +4373,7 @@ app.post('/upload_sprite', urlencodedParser, async (request, response) => {
|
||||
}
|
||||
|
||||
const filename = label + path.parse(file.originalname).ext;
|
||||
const spritePath = path.join("./uploads/", file.filename);
|
||||
const spritePath = path.join(UPLOADS_PATH, file.filename);
|
||||
const pathToFile = path.join(spritesPath, filename);
|
||||
// Copy uploaded file to sprites folder
|
||||
fs.cpSync(spritePath, pathToFile);
|
||||
|
Loading…
x
Reference in New Issue
Block a user