mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'staging' into persona-lorebook
This commit is contained in:
38
.github/readme-ru_ru.md
vendored
38
.github/readme-ru_ru.md
vendored
@@ -209,6 +209,44 @@ SillyTavern поддерживает расширения.
|
||||
5. Запустите лаунчер установки: `chmod +x install.sh && ./install.sh` and choose what you wanna install
|
||||
6. После завершения установки, запустите лаунчер следующей командой: `chmod +x launcher.sh && ./launcher.sh`
|
||||
|
||||
## 🐋 Установка с помощью Docker
|
||||
|
||||
Предполагается, что вы уже установили Docker, имеете доступ к командной строке для установки контейнеров и знакомы с их базовым управлением.
|
||||
|
||||
### Сборка образа самостоятельно
|
||||
|
||||
У нас есть подробное руководство по использованию SillyTavern в Docker [здесь](http://docs.sillytavern.app/installation/docker/), которое охватывает установку на Windows, macOS и Linux! Ознакомьтесь с ним, если хотите создать образ самостоятельно.
|
||||
|
||||
### Использование реестра контейнеров GitHub (самый простой способ)
|
||||
|
||||
Для работы SillyTavern вам понадобятся две обязательные настройки каталогов и одна настройка порта. В команде замените указанные значения на свои:
|
||||
|
||||
#### Переменные контейнера
|
||||
|
||||
##### Маппинг томов
|
||||
|
||||
* [config] - директория, где на вашем хосте будут храниться файлы конфигурации SillyTavern.
|
||||
* [data] - директория, где на вашем хосте будут храниться пользовательские данные SillyTavern (включая персонажей).
|
||||
* [plugins] - (необязательно) директория, где на вашем хосте будут храниться плагины сервера SillyTavern.
|
||||
|
||||
##### Маппинг портов
|
||||
|
||||
* [PublicPort] - Порт, через который будет передаваться трафик. Это обязательно, так как вы будете обращаться к контейнеру извне его виртуальной машины. НЕ ОТКРЫВАЙТЕ этот порт в интернет без реализации дополнительного уровня безопасности.
|
||||
|
||||
##### Дополнительные настройки
|
||||
|
||||
* [DockerNet] - Docker сеть, к которой контейнер должен быть подключен. Если вы не знаете, что это, обратитесь к [официальной документации Docker](https://docs.docker.com/reference/cli/docker/network/).
|
||||
* [version] - на правой части этой страницы GitHub вы найдете раздел "Packages". Выберите пакет "sillytavern", чтобы увидеть версии образов. Тег "latest" позволит вам обновляться до текущего релиза. Также доступны теги "staging" и "release", которые соответствуют ночным сборкам соответствующих веток. Однако это может быть нецелесообразно, если вы используете расширения, которые могут ломаться и требуют времени для обновления.
|
||||
|
||||
#### Команда установки
|
||||
|
||||
1. Откройте командную строку
|
||||
2. Выполните следующую команду
|
||||
|
||||
`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]'`
|
||||
|
||||
> Заметьте, что 8000 является портом по умолчанию. Не забудьте использовать соответствующий порт, если вы измените его в конфиге.
|
||||
|
||||
## 📱 Мобильные устройства - Установка при помощи termux
|
||||
|
||||
> **ОБРАТИТЕ ВНИМАНИЕ!**
|
||||
|
@@ -669,7 +669,7 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai">
|
||||
<div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt">
|
||||
<div class="range-block-title" data-i18n="Temperature">
|
||||
Temperature
|
||||
</div>
|
||||
@@ -682,7 +682,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai">
|
||||
<div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt">
|
||||
<div class="range-block-title" data-i18n="Frequency Penalty">
|
||||
Frequency Penalty
|
||||
</div>
|
||||
@@ -695,7 +695,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai">
|
||||
<div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt">
|
||||
<div class="range-block-title" data-i18n="Presence Penalty">
|
||||
Presence Penalty
|
||||
</div>
|
||||
@@ -721,7 +721,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai">
|
||||
<div class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt">
|
||||
<div class="range-block-title" data-i18n="Top P">
|
||||
Top P
|
||||
</div>
|
||||
@@ -958,7 +958,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq">
|
||||
<div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,nanogpt">
|
||||
<div class="range-block-title justifyLeft" data-i18n="Seed">
|
||||
Seed
|
||||
</div>
|
||||
|
@@ -241,7 +241,7 @@ import { hideLoader, showLoader } from './scripts/loader.js';
|
||||
import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js';
|
||||
import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels, loadTabbyModels } from './scripts/textgen-models.js';
|
||||
import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId, preserveNeutralChat, restoreNeutralChat } from './scripts/chats.js';
|
||||
import { initPresetManager } from './scripts/preset-manager.js';
|
||||
import { getPresetManager, initPresetManager } from './scripts/preset-manager.js';
|
||||
import { MacrosParser, evaluateMacros, getLastMessageId, initMacros } from './scripts/macros.js';
|
||||
import { currentUser, setUserControls } from './scripts/user.js';
|
||||
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
|
||||
instruction: main_api !== 'openai' && power_user.sysprompt.enabled ? substituteParams(power_user.prefer_character_prompt && system ? system : power_user.sysprompt.content) : '',
|
||||
userPersona: (power_user.persona_description_position == persona_description_positions.IN_PROMPT ? (persona || '') : ''),
|
||||
tokenizer: getFriendlyTokenizerName(main_api).tokenizerName || '',
|
||||
presetName: getPresetManager()?.getSelectedPresetName() || '',
|
||||
};
|
||||
|
||||
//console.log(additionalPromptStuff);
|
||||
@@ -5160,6 +5161,7 @@ export async function itemizedParams(itemizedPrompts, thisPromptSet, incomingMes
|
||||
dataBankVectorsStringTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].dataBankVectorsString),
|
||||
modelUsed: chat[incomingMesId]?.extra?.model,
|
||||
apiUsed: chat[incomingMesId]?.extra?.api,
|
||||
presetName: itemizedPrompts[thisPromptSet].presetName || t`(Unknown)`,
|
||||
};
|
||||
|
||||
const getFriendlyName = (value) => $(`#rm_api_block select option[value="${value}"]`).first().text() || value;
|
||||
@@ -7752,25 +7754,31 @@ export async function saveChatConditional() {
|
||||
}
|
||||
}
|
||||
|
||||
async function importCharacterChat(formData) {
|
||||
await jQuery.ajax({
|
||||
type: 'POST',
|
||||
url: '/api/chats/import',
|
||||
data: formData,
|
||||
beforeSend: function () {
|
||||
},
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: async function (data) {
|
||||
if (data.res) {
|
||||
await displayPastChats();
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
$('#create_button').removeAttr('disabled');
|
||||
},
|
||||
/**
|
||||
* Saves the chat to the server.
|
||||
* @param {FormData} formData Form data to send to the server.
|
||||
* @param {EventTarget} eventTarget Event target to trigger the event on.
|
||||
*/
|
||||
async function importCharacterChat(formData, eventTarget) {
|
||||
const headers = getRequestHeaders();
|
||||
delete headers['Content-Type'];
|
||||
const fetchResult = await fetch('/api/chats/import', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: headers,
|
||||
cache: 'no-cache',
|
||||
});
|
||||
|
||||
if (fetchResult.ok) {
|
||||
const data = await fetchResult.json();
|
||||
if (data.res) {
|
||||
await displayPastChats();
|
||||
}
|
||||
}
|
||||
|
||||
if (eventTarget instanceof HTMLInputElement) {
|
||||
eventTarget.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function updateViewMessageIds(startFromZero = false) {
|
||||
@@ -10827,13 +10835,13 @@ jQuery(async function () {
|
||||
});
|
||||
|
||||
$('#chat_import_file').on('change', async function (e) {
|
||||
var file = e.target.files[0];
|
||||
const file = e.target.files[0];
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
var ext = file.name.match(/\.(\w+)$/);
|
||||
const ext = file.name.match(/\.(\w+)$/);
|
||||
if (
|
||||
!ext ||
|
||||
(ext[1].toLowerCase() != 'json' && ext[1].toLowerCase() != 'jsonl')
|
||||
@@ -10846,17 +10854,17 @@ jQuery(async function () {
|
||||
return;
|
||||
}
|
||||
|
||||
var format = ext[1].toLowerCase();
|
||||
const format = ext[1].toLowerCase();
|
||||
$('#chat_import_file_type').val(format);
|
||||
|
||||
var formData = new FormData($('#form_import_chat').get(0));
|
||||
const formData = new FormData($('#form_import_chat').get(0));
|
||||
formData.append('user_name', name1);
|
||||
$('#select_chat_div').html('');
|
||||
|
||||
if (selected_group) {
|
||||
await importGroupChat(formData);
|
||||
await importGroupChat(formData, e.originalEvent.target);
|
||||
} else {
|
||||
await importCharacterChat(formData);
|
||||
await importCharacterChat(formData, e.originalEvent.target);
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -1863,32 +1863,38 @@ export async function deleteGroupChat(groupId, chatId) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function importGroupChat(formData) {
|
||||
await jQuery.ajax({
|
||||
type: 'POST',
|
||||
url: '/api/chats/group/import',
|
||||
data: formData,
|
||||
beforeSend: function () {
|
||||
},
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: async function (data) {
|
||||
if (data.res) {
|
||||
const chatId = data.res;
|
||||
const group = groups.find(x => x.id == selected_group);
|
||||
|
||||
if (group) {
|
||||
group.chats.push(chatId);
|
||||
await editGroup(selected_group, true, true);
|
||||
await displayPastChats();
|
||||
}
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
$('#create_button').removeAttr('disabled');
|
||||
},
|
||||
/**
|
||||
* Imports a group chat from a file and adds it to the group.
|
||||
* @param {FormData} formData Form data to send to the server
|
||||
* @param {EventTarget} eventTarget Element that triggered the import
|
||||
*/
|
||||
export async function importGroupChat(formData, eventTarget) {
|
||||
const headers = getRequestHeaders();
|
||||
delete headers['Content-Type'];
|
||||
const fetchResult = await fetch('/api/chats/group/import', {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: formData,
|
||||
cache: 'no-cache',
|
||||
});
|
||||
|
||||
if (fetchResult.ok) {
|
||||
const data = await fetchResult.json();
|
||||
if (data.res) {
|
||||
const chatId = data.res;
|
||||
const group = groups.find(x => x.id == selected_group);
|
||||
|
||||
if (group) {
|
||||
group.chats.push(chatId);
|
||||
await editGroup(selected_group, true, true);
|
||||
await displayPastChats();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (eventTarget instanceof HTMLInputElement) {
|
||||
eventTarget.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveGroupBookmarkChat(groupId, name, metadata, mesId) {
|
||||
|
@@ -1812,6 +1812,7 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
const isPerplexity = oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY;
|
||||
const isGroq = oai_settings.chat_completion_source == chat_completion_sources.GROQ;
|
||||
const is01AI = oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI;
|
||||
const isNano = oai_settings.chat_completion_source == chat_completion_sources.NANOGPT;
|
||||
const isTextCompletion = isOAI && textCompletionModels.includes(oai_settings.openai_model);
|
||||
const isQuiet = type === 'quiet';
|
||||
const isImpersonate = type === 'impersonate';
|
||||
@@ -1971,7 +1972,7 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
delete generate_data.stop;
|
||||
}
|
||||
|
||||
if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere) && oai_settings.seed >= 0) {
|
||||
if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere || isNano) && oai_settings.seed >= 0) {
|
||||
generate_data['seed'] = oai_settings.seed;
|
||||
}
|
||||
|
||||
|
@@ -269,7 +269,7 @@ export function initDefaultSlashCommands() {
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'at',
|
||||
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 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.',
|
||||
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.',
|
||||
typeList: [ARGUMENT_TYPE.NUMBER],
|
||||
enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
|
||||
}),
|
||||
@@ -325,7 +325,7 @@ export function initDefaultSlashCommands() {
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'at',
|
||||
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 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.',
|
||||
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.',
|
||||
typeList: [ARGUMENT_TYPE.NUMBER],
|
||||
enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
|
||||
}),
|
||||
@@ -388,7 +388,7 @@ export function initDefaultSlashCommands() {
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'at',
|
||||
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 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.',
|
||||
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.',
|
||||
typeList: [ARGUMENT_TYPE.NUMBER],
|
||||
enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
|
||||
}),
|
||||
@@ -606,7 +606,7 @@ export function initDefaultSlashCommands() {
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'at',
|
||||
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 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.',
|
||||
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.',
|
||||
typeList: [ARGUMENT_TYPE.NUMBER],
|
||||
enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
|
||||
}),
|
||||
@@ -3025,7 +3025,7 @@ async function sendUserMessageCallback(args, text) {
|
||||
let insertAt = Number(args?.at);
|
||||
|
||||
// Convert possible depth parameter to index
|
||||
if (!isNaN(insertAt) && (insertAt < 0 || insertAt === Number(-0))) {
|
||||
if (!isNaN(insertAt) && (insertAt < 0 || Object.is(insertAt, -0))) {
|
||||
// Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7)
|
||||
insertAt = chat.length + insertAt;
|
||||
}
|
||||
@@ -3399,7 +3399,7 @@ export async function sendMessageAs(args, text) {
|
||||
let insertAt = Number(args.at);
|
||||
|
||||
// Convert possible depth parameter to index
|
||||
if (!isNaN(insertAt) && (insertAt < 0 || insertAt === Number(-0))) {
|
||||
if (!isNaN(insertAt) && (insertAt < 0 || Object.is(insertAt, -0))) {
|
||||
// Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7)
|
||||
insertAt = chat.length + insertAt;
|
||||
}
|
||||
@@ -3453,7 +3453,7 @@ export async function sendNarratorMessage(args, text) {
|
||||
let insertAt = Number(args.at);
|
||||
|
||||
// Convert possible depth parameter to index
|
||||
if (!isNaN(insertAt) && (insertAt < 0 || insertAt === Number(-0))) {
|
||||
if (!isNaN(insertAt) && (insertAt < 0 || Object.is(insertAt, -0))) {
|
||||
// Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7)
|
||||
insertAt = chat.length + insertAt;
|
||||
}
|
||||
@@ -3542,7 +3542,7 @@ async function sendCommentMessage(args, text) {
|
||||
let insertAt = Number(args.at);
|
||||
|
||||
// Convert possible depth parameter to index
|
||||
if (!isNaN(insertAt) && (insertAt < 0 || insertAt === Number(-0))) {
|
||||
if (!isNaN(insertAt) && (insertAt < 0 || Object.is(insertAt, -0))) {
|
||||
// Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7)
|
||||
insertAt = chat.length + insertAt;
|
||||
}
|
||||
|
@@ -4,8 +4,17 @@
|
||||
<div id="copyPromptToClipboard" class="fa-solid fa-copy menu_button" title="Copy Prompt" data-i18n="[title]Copy Prompt"></div>
|
||||
<div id="diffPrevPrompt" class="fa-solid fa-code-compare menu_button" title="Show Prompt Differences" data-i18n="[title]Show Prompt Differences"></div>
|
||||
</h3>
|
||||
API/Model Used: {{mainApiFriendlyName}} {{#if apiUsed}}({{apiUsed}}){{/if}} {{#if modelUsed}}– {{modelUsed}}{{/if}}<br>
|
||||
Tokenizer: {{selectedTokenizer}}<br>
|
||||
<div>
|
||||
<div>
|
||||
API/Model: {{mainApiFriendlyName}} {{#if apiUsed}}({{apiUsed}}){{/if}} {{#if modelUsed}}– {{modelUsed}}{{/if}}
|
||||
</div>
|
||||
<div>
|
||||
<small>Preset: {{presetName}}</small>
|
||||
<span>|</span>
|
||||
<small>Tokenizer: {{selectedTokenizer}}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span class="tokenItemizingSubclass">
|
||||
Only the white numbers really matter. All numbers are estimates.
|
||||
Grey color items may not have been included in the context due to certain prompt format settings.
|
||||
|
@@ -4,8 +4,17 @@
|
||||
<div id="copyPromptToClipboard" class="fa-solid fa-copy menu_button" title="Copy Prompt" data-i18n="[title]Copy Prompt"></div>
|
||||
<div id="diffPrevPrompt" class="fa-solid fa-code-compare menu_button" title="Show Prompt Differences" data-i18n="[title]Show Prompt Differences"></div>
|
||||
</h3>
|
||||
API/Model Used: {{mainApiFriendlyName}} {{#if apiUsed}}({{apiUsed}}){{/if}} {{#if modelUsed}}– {{modelUsed}}{{/if}}<br>
|
||||
Tokenizer: {{selectedTokenizer}}<br>
|
||||
<div>
|
||||
<div>
|
||||
API/Model: {{mainApiFriendlyName}} {{#if apiUsed}}({{apiUsed}}){{/if}} {{#if modelUsed}}– {{modelUsed}}{{/if}}
|
||||
</div>
|
||||
<div>
|
||||
<small>Preset: {{presetName}}</small>
|
||||
<span>|</span>
|
||||
<small>Tokenizer: {{selectedTokenizer}}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span class="tokenItemizingSubclass">
|
||||
Only the white numbers really matter. All numbers are estimates.
|
||||
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,
|
||||
'dry_sequence_breakers': sequenceBreakers,
|
||||
};
|
||||
params = Object.assign(params, llamaCppParams);
|
||||
if (!Array.isArray(sequenceBreakers) || sequenceBreakers.length === 0) {
|
||||
delete params.dry_sequence_breakers;
|
||||
}
|
||||
}
|
||||
|
||||
eventSource.emitAndWait(event_types.TEXT_COMPLETION_SETTINGS_READY, params);
|
||||
|
@@ -190,6 +190,44 @@ function importCAIChat(userName, characterName, jsonData) {
|
||||
return newChats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports a chat from Kobold Lite format.
|
||||
* @param {string} _userName User name
|
||||
* @param {string} _characterName Character name
|
||||
* @param {object} data JSON data
|
||||
* @returns {string} Chat data
|
||||
*/
|
||||
function importKoboldLiteChat(_userName, _characterName, data) {
|
||||
const inputToken = '{{[INPUT]}}';
|
||||
const outputToken = '{{[OUTPUT]}}';
|
||||
|
||||
/** @type {function(string): object} */
|
||||
function processKoboldMessage(msg) {
|
||||
const isUser = msg.includes(inputToken) || msg.includes(outputToken);
|
||||
return {
|
||||
name: isUser ? header.user_name : header.character_name,
|
||||
is_user: isUser,
|
||||
mes: msg.replace(inputToken, '').replace(outputToken, '').trim(),
|
||||
send_date: Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
// Create the header
|
||||
const header = {
|
||||
user_name: data.savedsettings.chatname,
|
||||
character_name: data.savedsettings.chatopponent,
|
||||
};
|
||||
// Format messages
|
||||
const formattedMessages = data.actions.map(processKoboldMessage);
|
||||
// Add prompt if available
|
||||
if (data.prompt) {
|
||||
formattedMessages.unshift(processKoboldMessage(data.prompt));
|
||||
}
|
||||
// Combine header and messages
|
||||
const chatData = [header, ...formattedMessages];
|
||||
return chatData.map(obj => JSON.stringify(obj)).join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Flattens `msg` and `swipes` data from Chub Chat format.
|
||||
* Only changes enough to make it compatible with the standard chat serialization format.
|
||||
@@ -413,7 +451,7 @@ router.post('/import', urlencodedParser, function (request, response) {
|
||||
const format = request.body.file_type;
|
||||
const avatarUrl = (request.body.avatar_url).replace('.png', '');
|
||||
const characterName = request.body.character_name;
|
||||
const userName = request.body.user_name || 'You';
|
||||
const userName = request.body.user_name || 'User';
|
||||
|
||||
if (!request.file) {
|
||||
return response.sendStatus(400);
|
||||
@@ -426,33 +464,38 @@ router.post('/import', urlencodedParser, function (request, response) {
|
||||
if (format === 'json') {
|
||||
fs.unlinkSync(pathToUpload);
|
||||
const jsonData = JSON.parse(data);
|
||||
if (jsonData.histories !== undefined) {
|
||||
// CAI Tools format
|
||||
const chats = importCAIChat(userName, characterName, jsonData);
|
||||
for (const chat of chats) {
|
||||
const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`;
|
||||
const filePath = path.join(request.user.directories.chats, avatarUrl, fileName);
|
||||
writeFileAtomicSync(filePath, chat, 'utf8');
|
||||
}
|
||||
return response.send({ res: true });
|
||||
} else if (Array.isArray(jsonData.data_visible)) {
|
||||
// oobabooga's format
|
||||
const chat = importOobaChat(userName, characterName, jsonData);
|
||||
const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`;
|
||||
const filePath = path.join(request.user.directories.chats, avatarUrl, fileName);
|
||||
writeFileAtomicSync(filePath, chat, 'utf8');
|
||||
return response.send({ res: true });
|
||||
} else if (Array.isArray(jsonData.messages)) {
|
||||
// Agnai format
|
||||
const chat = importAgnaiChat(userName, characterName, jsonData);
|
||||
const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`;
|
||||
const filePath = path.join(request.user.directories.chats, avatarUrl, fileName);
|
||||
writeFileAtomicSync(filePath, chat, 'utf8');
|
||||
return response.send({ res: true });
|
||||
} else {
|
||||
|
||||
/** @type {function(string, string, object): string|string[]} */
|
||||
let importFunc;
|
||||
|
||||
if (jsonData.savedsettings !== undefined) { // Kobold Lite format
|
||||
importFunc = importKoboldLiteChat;
|
||||
} else if (jsonData.histories !== undefined) { // CAI Tools format
|
||||
importFunc = importCAIChat;
|
||||
} else if (Array.isArray(jsonData.data_visible)) { // oobabooga's format
|
||||
importFunc = importOobaChat;
|
||||
} else if (Array.isArray(jsonData.messages)) { // Agnai's format
|
||||
importFunc = importAgnaiChat;
|
||||
} else { // Unknown format
|
||||
console.log('Incorrect chat format .json');
|
||||
return response.send({ error: true });
|
||||
}
|
||||
|
||||
const handleChat = (chat) => {
|
||||
const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`;
|
||||
const filePath = path.join(request.user.directories.chats, avatarUrl, fileName);
|
||||
writeFileAtomicSync(filePath, chat, 'utf8');
|
||||
};
|
||||
|
||||
const chat = importFunc(userName, characterName, jsonData);
|
||||
|
||||
if (Array.isArray(chat)) {
|
||||
chat.forEach(handleChat);
|
||||
} else {
|
||||
handleChat(chat);
|
||||
}
|
||||
|
||||
return response.send({ res: true });
|
||||
}
|
||||
|
||||
if (format === 'jsonl') {
|
||||
@@ -561,10 +604,14 @@ router.post('/search', jsonParser, function (request, response) {
|
||||
|
||||
let targetGroup;
|
||||
for (const groupFile of groupFiles) {
|
||||
const groupData = JSON.parse(fs.readFileSync(path.join(groupDir, groupFile), 'utf8'));
|
||||
if (groupData.id === group_id) {
|
||||
targetGroup = groupData;
|
||||
break;
|
||||
try {
|
||||
const groupData = JSON.parse(fs.readFileSync(path.join(groupDir, groupFile), 'utf8'));
|
||||
if (groupData.id === group_id) {
|
||||
targetGroup = groupData;
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(groupFile, 'group file is corrupted:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user