Merge branch 'staging' into persona-lorebook
| @@ -209,6 +209,44 @@ SillyTavern поддерживает расширения. | ||
| 209 | 209 | 5. Запустите лаунчер установки: `chmod +x install.sh && ./install.sh` and choose what you wanna install |
| 210 | 210 | 6. После завершения установки, запустите лаунчер следующей командой: `chmod +x launcher.sh && ./launcher.sh` |
| 211 | 211 | |
| 212 | +## 🐋 Установка с помощью Docker | |
| 213 | + | |
| 214 | +Предполагается, что вы уже установили Docker, имеете доступ к командной строке для установки контейнеров и знакомы с их базовым управлением. | |
| 215 | + | |
| 216 | +### Сборка образа самостоятельно | |
| 217 | + | |
| 218 | +У нас есть подробное руководство по использованию SillyTavern в Docker [здесь](http://docs.sillytavern.app/installation/docker/), которое охватывает установку на Windows, macOS и Linux! Ознакомьтесь с ним, если хотите создать образ самостоятельно. | |
| 219 | + | |
| 220 | +### Использование реестра контейнеров GitHub (самый простой способ) | |
| 221 | + | |
| 222 | +Для работы SillyTavern вам понадобятся две обязательные настройки каталогов и одна настройка порта. В команде замените указанные значения на свои: | |
| 223 | + | |
| 224 | +#### Переменные контейнера | |
| 225 | + | |
| 226 | +##### Маппинг томов | |
| 227 | + | |
| 228 | +* [config] - директория, где на вашем хосте будут храниться файлы конфигурации SillyTavern. | |
| 229 | +* [data] - директория, где на вашем хосте будут храниться пользовательские данные SillyTavern (включая персонажей). | |
| 230 | +* [plugins] - (необязательно) директория, где на вашем хосте будут храниться плагины сервера SillyTavern. | |
| 231 | + | |
| 232 | +##### Маппинг портов | |
| 233 | + | |
| 234 | +* [PublicPort] - Порт, через который будет передаваться трафик. Это обязательно, так как вы будете обращаться к контейнеру извне его виртуальной машины. НЕ ОТКРЫВАЙТЕ этот порт в интернет без реализации дополнительного уровня безопасности. | |
| 235 | + | |
| 236 | +##### Дополнительные настройки | |
| 237 | + | |
| 238 | +* [DockerNet] - Docker сеть, к которой контейнер должен быть подключен. Если вы не знаете, что это, обратитесь к [официальной документации Docker](https://docs.docker.com/reference/cli/docker/network/). | |
| 239 | +* [version] - на правой части этой страницы GitHub вы найдете раздел "Packages". Выберите пакет "sillytavern", чтобы увидеть версии образов. Тег "latest" позволит вам обновляться до текущего релиза. Также доступны теги "staging" и "release", которые соответствуют ночным сборкам соответствующих веток. Однако это может быть нецелесообразно, если вы используете расширения, которые могут ломаться и требуют времени для обновления. | |
| 240 | + | |
| 241 | +#### Команда установки | |
| 242 | + | |
| 243 | +1. Откройте командную строку | |
| 244 | +2. Выполните следующую команду | |
| 245 | + | |
| 246 | +`docker create --name='sillytavern' --net='[DockerNet]' -p '8000:8000/tcp' -v '[plugins]':'/home/node/app/plugins':'rw' -v '[config]':'/home/node/app/config':'rw' -v '[data]':'/home/node/app/data':'rw' 'ghcr.io/sillytavern/sillytavern:[version]'` | |
| 247 | + | |
| 248 | +> Заметьте, что 8000 является портом по умолчанию. Не забудьте использовать соответствующий порт, если вы измените его в конфиге. | |
| 249 | + | |
| 212 | 250 | ## 📱 Мобильные устройства - Установка при помощи termux |
| 213 | 251 | |
| 214 | 252 | > **ОБРАТИТЕ ВНИМАНИЕ!** |
| @@ -669,7 +669,7 @@ | ||
| 669 | 669 | </span> |
| 670 | 670 | </div> |
| 671 | 671 | </div> |
| 672 | 672 | <div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt"> |
| 673 | 673 | <div class="range-block-title" data-i18n="Temperature"> |
| 674 | 674 | Temperature |
| 675 | 675 | </div> |
| @@ -682,7 +682,7 @@ | ||
| 682 | 682 | </div> |
| 683 | 683 | </div> |
| 684 | 684 | </div> |
| 685 | 685 | <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt"> |
| 686 | 686 | <div class="range-block-title" data-i18n="Frequency Penalty"> |
| 687 | 687 | Frequency Penalty |
| 688 | 688 | </div> |
| @@ -695,7 +695,7 @@ | ||
| 695 | 695 | </div> |
| 696 | 696 | </div> |
| 697 | 697 | </div> |
| 698 | 698 | <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt"> |
| 699 | 699 | <div class="range-block-title" data-i18n="Presence Penalty"> |
| 700 | 700 | Presence Penalty |
| 701 | 701 | </div> |
| @@ -721,7 +721,7 @@ | ||
| 721 | 721 | </div> |
| 722 | 722 | </div> |
| 723 | 723 | </div> |
| 724 | 724 | <div class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt"> |
| 725 | 725 | <div class="range-block-title" data-i18n="Top P"> |
| 726 | 726 | Top P |
| 727 | 727 | </div> |
| @@ -958,7 +958,7 @@ | ||
| 958 | 958 | </div> |
| 959 | 959 | </div> |
| 960 | 960 | </div> |
| 961 | 961 | <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,nanogpt"> |
| 962 | 962 | <div class="range-block-title justifyLeft" data-i18n="Seed"> |
| 963 | 963 | Seed |
| 964 | 964 | </div> |
| @@ -241,7 +241,7 @@ import { hideLoader, showLoader } from './scripts/loader.js'; | ||
| 241 | 241 | import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js'; |
| 242 | 242 | import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels, loadTabbyModels } from './scripts/textgen-models.js'; |
| 243 | 243 | import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId, preserveNeutralChat, restoreNeutralChat } from './scripts/chats.js'; |
| 244 | 244 | import { getPresetManager, initPresetManager } from './scripts/preset-manager.js'; |
| 245 | 245 | import { MacrosParser, evaluateMacros, getLastMessageId, initMacros } from './scripts/macros.js'; |
| 246 | 246 | import { currentUser, setUserControls } from './scripts/user.js'; |
| 247 | 247 | import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js'; |
| @@ -4494,6 +4494,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | ||
| 4494 | 4494 | instruction: main_api !== 'openai' && power_user.sysprompt.enabled ? substituteParams(power_user.prefer_character_prompt && system ? system : power_user.sysprompt.content) : '', |
| 4495 | 4495 | userPersona: (power_user.persona_description_position == persona_description_positions.IN_PROMPT ? (persona || '') : ''), |
| 4496 | 4496 | tokenizer: getFriendlyTokenizerName(main_api).tokenizerName || '', |
| 4497 | + presetName: getPresetManager()?.getSelectedPresetName() || '', | |
| 4497 | 4498 | }; |
| 4498 | 4499 | |
| 4499 | 4500 | //console.log(additionalPromptStuff); |
| @@ -5160,6 +5161,7 @@ export async function itemizedParams(itemizedPrompts, thisPromptSet, incomingMes | ||
| 5160 | 5161 | dataBankVectorsStringTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].dataBankVectorsString), |
| 5161 | 5162 | modelUsed: chat[incomingMesId]?.extra?.model, |
| 5162 | 5163 | apiUsed: chat[incomingMesId]?.extra?.api, |
| 5164 | + presetName: itemizedPrompts[thisPromptSet].presetName || t`(Unknown)`, | |
| 5163 | 5165 | }; |
| 5164 | 5166 | |
| 5165 | 5167 | const getFriendlyName = (value) => $(`#rm_api_block select option[value="${value}"]`).first().text() || value; |
| @@ -7752,25 +7754,31 @@ export async function saveChatConditional() { | ||
| 7752 | 7754 | } |
| 7753 | 7755 | } |
| 7754 | 7756 | |
| 7755 | -async function importCharacterChat(formData) { | |
| 7757 | +/** | |
| 7756 | - await jQuery.ajax({ | |
| 7758 | + * Saves the chat to the server. | |
| 7757 | - type: 'POST', | |
| 7759 | + * @param {FormData} formData Form data to send to the server. | |
| 7758 | - url: '/api/chats/import', | |
| 7760 | + * @param {EventTarget} eventTarget Event target to trigger the event on. | |
| 7759 | - data: formData, | |
| 7761 | + */ | |
| 7760 | 7762 | beforeSend:async function importCharacterChat(formData, eventTarget) { |
| 7761 | - }, | |
| 7763 | + const headers = getRequestHeaders(); | |
| 7762 | - cache: false, | |
| 7764 | + delete headers['Content-Type']; | |
| 7763 | - contentType: false, | |
| 7765 | + const fetchResult = await fetch('/api/chats/import', { | |
| 7764 | 7766 | processDatamethod: false'POST', |
| 7765 | - success: async function (data) { | |
| 7767 | + body: formData, | |
| 7766 | - if (data.res) { | |
| 7768 | + headers: headers, | |
| 7767 | - await displayPastChats(); | |
| 7769 | + cache: 'no-cache', | |
| 7768 | - } | |
| 7769 | - }, | |
| 7770 | - error: function () { | |
| 7771 | - $('#create_button').removeAttr('disabled'); | |
| 7772 | - }, | |
| 7773 | 7770 | }); |
| 7771 | + | |
| 7772 | + if (fetchResult.ok) { | |
| 7773 | + const data = await fetchResult.json(); | |
| 7774 | + if (data.res) { | |
| 7775 | + await displayPastChats(); | |
| 7776 | + } | |
| 7777 | + } | |
| 7778 | + | |
| 7779 | + if (eventTarget instanceof HTMLInputElement) { | |
| 7780 | + eventTarget.value = ''; | |
| 7781 | + } | |
| 7774 | 7782 | } |
| 7775 | 7783 | |
| 7776 | 7784 | function updateViewMessageIds(startFromZero = false) { |
| @@ -10827,13 +10835,13 @@ jQuery(async function () { | ||
| 10827 | 10835 | }); |
| 10828 | 10836 | |
| 10829 | 10837 | $('#chat_import_file').on('change', async function (e) { |
| 10830 | 10838 | varconst file = e.target.files[0]; |
| 10831 | 10839 | |
| 10832 | 10840 | if (!file) { |
| 10833 | 10841 | return; |
| 10834 | 10842 | } |
| 10835 | 10843 | |
| 10836 | 10844 | varconst ext = file.name.match(/\.(\w+)$/); |
| 10837 | 10845 | if ( |
| 10838 | 10846 | !ext || |
| 10839 | 10847 | (ext[1].toLowerCase() != 'json' && ext[1].toLowerCase() != 'jsonl') |
| @@ -10846,17 +10854,17 @@ jQuery(async function () { | ||
| 10846 | 10854 | return; |
| 10847 | 10855 | } |
| 10848 | 10856 | |
| 10849 | 10857 | varconst format = ext[1].toLowerCase(); |
| 10850 | 10858 | $('#chat_import_file_type').val(format); |
| 10851 | 10859 | |
| 10852 | 10860 | varconst formData = new FormData($('#form_import_chat').get(0)); |
| 10853 | 10861 | formData.append('user_name', name1); |
| 10854 | 10862 | $('#select_chat_div').html(''); |
| 10855 | 10863 | |
| 10856 | 10864 | if (selected_group) { |
| 10857 | 10865 | await importGroupChat(formData, e.originalEvent.target); |
| 10858 | 10866 | } else { |
| 10859 | 10867 | await importCharacterChat(formData, e.originalEvent.target); |
| 10860 | 10868 | } |
| 10861 | 10869 | }); |
| 10862 | 10870 | |
| @@ -1863,32 +1863,38 @@ export async function deleteGroupChat(groupId, chatId) { | ||
| 1863 | 1863 | } |
| 1864 | 1864 | } |
| 1865 | 1865 | |
| 1866 | -export async function importGroupChat(formData) { | |
| 1866 | +/** | |
| 1867 | - await jQuery.ajax({ | |
| 1867 | + * Imports a group chat from a file and adds it to the group. | |
| 1868 | - type: 'POST', | |
| 1868 | + * @param {FormData} formData Form data to send to the server | |
| 1869 | - url: '/api/chats/group/import', | |
| 1869 | + * @param {EventTarget} eventTarget Element that triggered the import | |
| 1870 | - data: formData, | |
| 1870 | + */ | |
| 1871 | 1871 | beforeSend:export async function importGroupChat(formData, eventTarget) { |
| 1872 | - }, | |
| 1872 | + const headers = getRequestHeaders(); | |
| 1873 | - cache: false, | |
| 1873 | + delete headers['Content-Type']; | |
| 1874 | - contentType: false, | |
| 1874 | + const fetchResult = await fetch('/api/chats/group/import', { | |
| 1875 | 1875 | processDatamethod: false'POST', |
| 1876 | - success: async function (data) { | |
| 1876 | + headers: headers, | |
| 1877 | - if (data.res) { | |
| 1877 | + body: formData, | |
| 1878 | - const chatId = data.res; | |
| 1878 | + cache: 'no-cache', | |
| 1879 | - const group = groups.find(x => x.id == selected_group); | |
| 1880 | - | |
| 1881 | - if (group) { | |
| 1882 | - group.chats.push(chatId); | |
| 1883 | - await editGroup(selected_group, true, true); | |
| 1884 | - await displayPastChats(); | |
| 1885 | - } | |
| 1886 | - } | |
| 1887 | - }, | |
| 1888 | - error: function () { | |
| 1889 | - $('#create_button').removeAttr('disabled'); | |
| 1890 | - }, | |
| 1891 | 1879 | }); |
| 1880 | + | |
| 1881 | + if (fetchResult.ok) { | |
| 1882 | + const data = await fetchResult.json(); | |
| 1883 | + if (data.res) { | |
| 1884 | + const chatId = data.res; | |
| 1885 | + const group = groups.find(x => x.id == selected_group); | |
| 1886 | + | |
| 1887 | + if (group) { | |
| 1888 | + group.chats.push(chatId); | |
| 1889 | + await editGroup(selected_group, true, true); | |
| 1890 | + await displayPastChats(); | |
| 1891 | + } | |
| 1892 | + } | |
| 1893 | + } | |
| 1894 | + | |
| 1895 | + if (eventTarget instanceof HTMLInputElement) { | |
| 1896 | + eventTarget.value = ''; | |
| 1897 | + } | |
| 1892 | 1898 | } |
| 1893 | 1899 | |
| 1894 | 1900 | export async function saveGroupBookmarkChat(groupId, name, metadata, mesId) { |
| @@ -1812,6 +1812,7 @@ async function sendOpenAIRequest(type, messages, signal) { | ||
| 1812 | 1812 | const isPerplexity = oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY; |
| 1813 | 1813 | const isGroq = oai_settings.chat_completion_source == chat_completion_sources.GROQ; |
| 1814 | 1814 | const is01AI = oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI; |
| 1815 | + const isNano = oai_settings.chat_completion_source == chat_completion_sources.NANOGPT; | |
| 1815 | 1816 | const isTextCompletion = isOAI && textCompletionModels.includes(oai_settings.openai_model); |
| 1816 | 1817 | const isQuiet = type === 'quiet'; |
| 1817 | 1818 | const isImpersonate = type === 'impersonate'; |
| @@ -1971,7 +1972,7 @@ async function sendOpenAIRequest(type, messages, signal) { | ||
| 1971 | 1972 | delete generate_data.stop; |
| 1972 | 1973 | } |
| 1973 | 1974 | |
| 1974 | 1975 | if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere || isNano) && oai_settings.seed >= 0) { |
| 1975 | 1976 | generate_data['seed'] = oai_settings.seed; |
| 1976 | 1977 | } |
| 1977 | 1978 | |
| @@ -269,7 +269,7 @@ export function initDefaultSlashCommands() { | ||
| 269 | 269 | }), |
| 270 | 270 | SlashCommandNamedArgument.fromProps({ |
| 271 | 271 | name: 'at', |
| 272 | 272 | description: 'position to insert the message (index-based, corresponding to message id). If not set, the message will be inserted at the end of the chat.\nNegative values (including -0) are accepted and will work similarly to how \'depth\' usually works. For example, -1 will insert the message right before the last message in chat.', |
| 273 | 273 | typeList: [ARGUMENT_TYPE.NUMBER], |
| 274 | 274 | enumProvider: commonEnumProviders.messages({ allowIdAfter: true }), |
| 275 | 275 | }), |
| @@ -325,7 +325,7 @@ export function initDefaultSlashCommands() { | ||
| 325 | 325 | ), |
| 326 | 326 | SlashCommandNamedArgument.fromProps({ |
| 327 | 327 | name: 'at', |
| 328 | 328 | description: 'position to insert the message (index-based, corresponding to message id). If not set, the message will be inserted at the end of the chat.\nNegative values (including -0) are accepted and will work similarly to how \'depth\' usually works. For example, -1 will insert the message right before the last message in chat.', |
| 329 | 329 | typeList: [ARGUMENT_TYPE.NUMBER], |
| 330 | 330 | enumProvider: commonEnumProviders.messages({ allowIdAfter: true }), |
| 331 | 331 | }), |
| @@ -388,7 +388,7 @@ export function initDefaultSlashCommands() { | ||
| 388 | 388 | ), |
| 389 | 389 | SlashCommandNamedArgument.fromProps({ |
| 390 | 390 | name: 'at', |
| 391 | 391 | description: 'position to insert the message (index-based, corresponding to message id). If not set, the message will be inserted at the end of the chat.\nNegative values (including -0) are accepted and will work similarly to how \'depth\' usually works. For example, -1 will insert the message right before the last message in chat.', |
| 392 | 392 | typeList: [ARGUMENT_TYPE.NUMBER], |
| 393 | 393 | enumProvider: commonEnumProviders.messages({ allowIdAfter: true }), |
| 394 | 394 | }), |
| @@ -606,7 +606,7 @@ export function initDefaultSlashCommands() { | ||
| 606 | 606 | ), |
| 607 | 607 | SlashCommandNamedArgument.fromProps({ |
| 608 | 608 | name: 'at', |
| 609 | 609 | description: 'position to insert the message (index-based, corresponding to message id). If not set, the message will be inserted at the end of the chat.\nNegative values (including -0) are accepted and will work similarly to how \'depth\' usually works. For example, -1 will insert the message right before the last message in chat.', |
| 610 | 610 | typeList: [ARGUMENT_TYPE.NUMBER], |
| 611 | 611 | enumProvider: commonEnumProviders.messages({ allowIdAfter: true }), |
| 612 | 612 | }), |
| @@ -3025,7 +3025,7 @@ async function sendUserMessageCallback(args, text) { | ||
| 3025 | 3025 | let insertAt = Number(args?.at); |
| 3026 | 3026 | |
| 3027 | 3027 | // Convert possible depth parameter to index |
| 3028 | 3028 | if (!isNaN(insertAt) && (insertAt < 0 || Object.is(insertAt ===, Number(-0))) { |
| 3029 | 3029 | // Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7) |
| 3030 | 3030 | insertAt = chat.length + insertAt; |
| 3031 | 3031 | } |
| @@ -3399,7 +3399,7 @@ export async function sendMessageAs(args, text) { | ||
| 3399 | 3399 | let insertAt = Number(args.at); |
| 3400 | 3400 | |
| 3401 | 3401 | // Convert possible depth parameter to index |
| 3402 | 3402 | if (!isNaN(insertAt) && (insertAt < 0 || Object.is(insertAt ===, Number(-0))) { |
| 3403 | 3403 | // Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7) |
| 3404 | 3404 | insertAt = chat.length + insertAt; |
| 3405 | 3405 | } |
| @@ -3453,7 +3453,7 @@ export async function sendNarratorMessage(args, text) { | ||
| 3453 | 3453 | let insertAt = Number(args.at); |
| 3454 | 3454 | |
| 3455 | 3455 | // Convert possible depth parameter to index |
| 3456 | 3456 | if (!isNaN(insertAt) && (insertAt < 0 || Object.is(insertAt ===, Number(-0))) { |
| 3457 | 3457 | // Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7) |
| 3458 | 3458 | insertAt = chat.length + insertAt; |
| 3459 | 3459 | } |
| @@ -3542,7 +3542,7 @@ async function sendCommentMessage(args, text) { | ||
| 3542 | 3542 | let insertAt = Number(args.at); |
| 3543 | 3543 | |
| 3544 | 3544 | // Convert possible depth parameter to index |
| 3545 | 3545 | if (!isNaN(insertAt) && (insertAt < 0 || Object.is(insertAt ===, Number(-0))) { |
| 3546 | 3546 | // Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7) |
| 3547 | 3547 | insertAt = chat.length + insertAt; |
| 3548 | 3548 | } |
| @@ -4,8 +4,17 @@ | ||
| 4 | 4 | <div id="copyPromptToClipboard" class="fa-solid fa-copy menu_button" title="Copy Prompt" data-i18n="[title]Copy Prompt"></div> |
| 5 | 5 | <div id="diffPrevPrompt" class="fa-solid fa-code-compare menu_button" title="Show Prompt Differences" data-i18n="[title]Show Prompt Differences"></div> |
| 6 | 6 | </h3> |
| 7 | -API/Model Used: {{mainApiFriendlyName}} {{#if apiUsed}}({{apiUsed}}){{/if}} {{#if modelUsed}}– {{modelUsed}}{{/if}}<br> | |
| 7 | +<div> | |
| 8 | -Tokenizer: {{selectedTokenizer}}<br> | |
| 8 | + <div> | |
| 9 | + API/Model: {{mainApiFriendlyName}} {{#if apiUsed}}({{apiUsed}}){{/if}} {{#if modelUsed}}– {{modelUsed}}{{/if}} | |
| 10 | + </div> | |
| 11 | + <div> | |
| 12 | + <small>Preset: {{presetName}}</small> | |
| 13 | + <span>|</span> | |
| 14 | + <small>Tokenizer: {{selectedTokenizer}}</small> | |
| 15 | + </div> | |
| 16 | +</div> | |
| 17 | + | |
| 9 | 18 | <span class="tokenItemizingSubclass"> |
| 10 | 19 | Only the white numbers really matter. All numbers are estimates. |
| 11 | 20 | Grey color items may not have been included in the context due to certain prompt format settings. |
| @@ -4,8 +4,17 @@ | ||
| 4 | 4 | <div id="copyPromptToClipboard" class="fa-solid fa-copy menu_button" title="Copy Prompt" data-i18n="[title]Copy Prompt"></div> |
| 5 | 5 | <div id="diffPrevPrompt" class="fa-solid fa-code-compare menu_button" title="Show Prompt Differences" data-i18n="[title]Show Prompt Differences"></div> |
| 6 | 6 | </h3> |
| 7 | -API/Model Used: {{mainApiFriendlyName}} {{#if apiUsed}}({{apiUsed}}){{/if}} {{#if modelUsed}}– {{modelUsed}}{{/if}}<br> | |
| 7 | +<div> | |
| 8 | -Tokenizer: {{selectedTokenizer}}<br> | |
| 8 | + <div> | |
| 9 | + API/Model: {{mainApiFriendlyName}} {{#if apiUsed}}({{apiUsed}}){{/if}} {{#if modelUsed}}– {{modelUsed}}{{/if}} | |
| 10 | + </div> | |
| 11 | + <div> | |
| 12 | + <small>Preset: {{presetName}}</small> | |
| 13 | + <span>|</span> | |
| 14 | + <small>Tokenizer: {{selectedTokenizer}}</small> | |
| 15 | + </div> | |
| 16 | +</div> | |
| 17 | + | |
| 9 | 18 | <span class="tokenItemizingSubclass"> |
| 10 | 19 | Only the white numbers really matter. All numbers are estimates. |
| 11 | 20 | Grey color items may not have been included in the context due to certain prompt format settings. |
| @@ -1340,6 +1340,9 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, | ||
| 1340 | 1340 | 'dry_sequence_breakers': sequenceBreakers, |
| 1341 | 1341 | }; |
| 1342 | 1342 | params = Object.assign(params, llamaCppParams); |
| 1343 | + if (!Array.isArray(sequenceBreakers) || sequenceBreakers.length === 0) { | |
| 1344 | + delete params.dry_sequence_breakers; | |
| 1345 | + } | |
| 1343 | 1346 | } |
| 1344 | 1347 | |
| 1345 | 1348 | eventSource.emitAndWait(event_types.TEXT_COMPLETION_SETTINGS_READY, params); |
| @@ -191,6 +191,44 @@ function importCAIChat(userName, characterName, jsonData) { | ||
| 191 | 191 | } |
| 192 | 192 | |
| 193 | 193 | /** |
| 194 | + * Imports a chat from Kobold Lite format. | |
| 195 | + * @param {string} _userName User name | |
| 196 | + * @param {string} _characterName Character name | |
| 197 | + * @param {object} data JSON data | |
| 198 | + * @returns {string} Chat data | |
| 199 | + */ | |
| 200 | +function importKoboldLiteChat(_userName, _characterName, data) { | |
| 201 | + const inputToken = '{{[INPUT]}}'; | |
| 202 | + const outputToken = '{{[OUTPUT]}}'; | |
| 203 | + | |
| 204 | + /** @type {function(string): object} */ | |
| 205 | + function processKoboldMessage(msg) { | |
| 206 | + const isUser = msg.includes(inputToken) || msg.includes(outputToken); | |
| 207 | + return { | |
| 208 | + name: isUser ? header.user_name : header.character_name, | |
| 209 | + is_user: isUser, | |
| 210 | + mes: msg.replace(inputToken, '').replace(outputToken, '').trim(), | |
| 211 | + send_date: Date.now(), | |
| 212 | + }; | |
| 213 | + } | |
| 214 | + | |
| 215 | + // Create the header | |
| 216 | + const header = { | |
| 217 | + user_name: data.savedsettings.chatname, | |
| 218 | + character_name: data.savedsettings.chatopponent, | |
| 219 | + }; | |
| 220 | + // Format messages | |
| 221 | + const formattedMessages = data.actions.map(processKoboldMessage); | |
| 222 | + // Add prompt if available | |
| 223 | + if (data.prompt) { | |
| 224 | + formattedMessages.unshift(processKoboldMessage(data.prompt)); | |
| 225 | + } | |
| 226 | + // Combine header and messages | |
| 227 | + const chatData = [header, ...formattedMessages]; | |
| 228 | + return chatData.map(obj => JSON.stringify(obj)).join('\n'); | |
| 229 | +} | |
| 230 | + | |
| 231 | +/** | |
| 194 | 232 | * Flattens `msg` and `swipes` data from Chub Chat format. |
| 195 | 233 | * Only changes enough to make it compatible with the standard chat serialization format. |
| 196 | 234 | * @param {string} userName User name |
| @@ -413,7 +451,7 @@ router.post('/import', urlencodedParser, function (request, response) { | ||
| 413 | 451 | const format = request.body.file_type; |
| 414 | 452 | const avatarUrl = (request.body.avatar_url).replace('.png', ''); |
| 415 | 453 | const characterName = request.body.character_name; |
| 416 | 454 | const userName = request.body.user_name || 'YouUser'; |
| 417 | 455 | |
| 418 | 456 | if (!request.file) { |
| 419 | 457 | return response.sendStatus(400); |
| @@ -426,33 +464,38 @@ router.post('/import', urlencodedParser, function (request, response) { | ||
| 426 | 464 | if (format === 'json') { |
| 427 | 465 | fs.unlinkSync(pathToUpload); |
| 428 | 466 | const jsonData = JSON.parse(data); |
| 429 | - if (jsonData.histories !== undefined) { | |
| 467 | + | |
| 430 | - // CAI Tools format | |
| 468 | + /** @type {function(string, string, object): string|string[]} */ | |
| 431 | - const chats = importCAIChat(userName, characterName, jsonData); | |
| 469 | + let importFunc; | |
| 432 | - for (const chat of chats) { | |
| 470 | + | |
| 433 | - const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`; | |
| 471 | + if (jsonData.savedsettings !== undefined) { // Kobold Lite format | |
| 434 | - const filePath = path.join(request.user.directories.chats, avatarUrl, fileName); | |
| 472 | + importFunc = importKoboldLiteChat; | |
| 435 | - writeFileAtomicSync(filePath, chat, 'utf8'); | |
| 473 | + } else if (jsonData.histories !== undefined) { // CAI Tools format | |
| 436 | - } | |
| 474 | + importFunc = importCAIChat; | |
| 437 | - return response.send({ res: true }); | |
| 475 | + } else if (Array.isArray(jsonData.data_visible)) { // oobabooga's format | |
| 438 | - } else if (Array.isArray(jsonData.data_visible)) { | |
| 476 | + importFunc = importOobaChat; | |
| 439 | - // oobabooga's format | |
| 477 | + } else if (Array.isArray(jsonData.messages)) { // Agnai's format | |
| 440 | - const chat = importOobaChat(userName, characterName, jsonData); | |
| 478 | + importFunc = importAgnaiChat; | |
| 441 | - const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`; | |
| 479 | + } else { // Unknown format | |
| 442 | - const filePath = path.join(request.user.directories.chats, avatarUrl, fileName); | |
| 480 | + console.log('Incorrect chat format .json'); | |
| 443 | - writeFileAtomicSync(filePath, chat, 'utf8'); | |
| 481 | + return response.send({ error: true }); | |
| 444 | - return response.send({ res: true }); | |
| 482 | + } | |
| 445 | - } else if (Array.isArray(jsonData.messages)) { | |
| 483 | + | |
| 446 | - // Agnai format | |
| 484 | + const handleChat = (chat) => { | |
| 447 | - const chat = importAgnaiChat(userName, characterName, jsonData); | |
| 448 | 485 | const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`; |
| 449 | 486 | const filePath = path.join(request.user.directories.chats, avatarUrl, fileName); |
| 450 | 487 | writeFileAtomicSync(filePath, chat, 'utf8'); |
| 451 | - return response.send({ res: true }); | |
| 488 | + }; | |
| 489 | + | |
| 490 | + const chat = importFunc(userName, characterName, jsonData); | |
| 491 | + | |
| 492 | + if (Array.isArray(chat)) { | |
| 493 | + chat.forEach(handleChat); | |
| 452 | 494 | } else { |
| 453 | - console.log('Incorrect chat format .json'); | |
| 495 | + handleChat(chat); | |
| 454 | - return response.send({ error: true }); | |
| 455 | 496 | } |
| 497 | + | |
| 498 | + return response.send({ res: true }); | |
| 456 | 499 | } |
| 457 | 500 | |
| 458 | 501 | if (format === 'jsonl') { |
| @@ -561,10 +604,14 @@ router.post('/search', jsonParser, function (request, response) { | ||
| 561 | 604 | |
| 562 | 605 | let targetGroup; |
| 563 | 606 | for (const groupFile of groupFiles) { |
| 564 | - const groupData = JSON.parse(fs.readFileSync(path.join(groupDir, groupFile), 'utf8')); | |
| 607 | + try { | |
| 565 | - if (groupData.id === group_id) { | |
| 608 | + const groupData = JSON.parse(fs.readFileSync(path.join(groupDir, groupFile), 'utf8')); | |
| 566 | - targetGroup = groupData; | |
| 609 | + if (groupData.id === group_id) { | |
| 567 | - break; | |
| 610 | + targetGroup = groupData; | |
| 611 | + break; | |
| 612 | + } | |
| 613 | + } catch (error) { | |
| 614 | + console.error(groupFile, 'group file is corrupted:', error); | |
| 568 | 615 | } |
| 569 | 616 | } |
| 570 | 617 | |