Merge branch 'staging' into persona-lorebook

efe25ddc04a0340cb60473189d8d84e5dae7389e

Cohee <18619528+Cohee1207@users.noreply.github.com>

10 files changed, +216 -95Ignore whitespace
.github/readme-ru_ru.md+38 -0
@@ -209,6 +209,44 @@ SillyTavern поддерживает расширения.
2092095. Запустите лаунчер установки: `chmod +x install.sh && ./install.sh` and choose what you wanna install
2102106. После завершения установки, запустите лаунчер следующей командой: `chmod +x launcher.sh && ./launcher.sh`
211211
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+
212250## 📱 Мобильные устройства - Установка при помощи termux
213251
214252> **ОБРАТИТЕ ВНИМАНИЕ!**
public/index.html+5 -5
@@ -669,7 +669,7 @@
669669 </span>
670670 </div>
671671 </div>
672672 <div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt">
673673 <div class="range-block-title" data-i18n="Temperature">
674674 Temperature
675675 </div>
@@ -682,7 +682,7 @@
682682 </div>
683683 </div>
684684 </div>
685685 <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt">
686686 <div class="range-block-title" data-i18n="Frequency Penalty">
687687 Frequency Penalty
688688 </div>
@@ -695,7 +695,7 @@
695695 </div>
696696 </div>
697697 </div>
698698 <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt">
699699 <div class="range-block-title" data-i18n="Presence Penalty">
700700 Presence Penalty
701701 </div>
@@ -721,7 +721,7 @@
721721 </div>
722722 </div>
723723 </div>
724724 <div class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt">
725725 <div class="range-block-title" data-i18n="Top P">
726726 Top P
727727 </div>
@@ -958,7 +958,7 @@
958958 </div>
959959 </div>
960960 </div>
961961 <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,nanogpt">
962962 <div class="range-block-title justifyLeft" data-i18n="Seed">
963963 Seed
964964 </div>
public/script.js+33 -25
@@ -241,7 +241,7 @@ import { hideLoader, showLoader } from './scripts/loader.js';
241241import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js';
242242import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels, loadTabbyModels } from './scripts/textgen-models.js';
243243import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId, preserveNeutralChat, restoreNeutralChat } from './scripts/chats.js';
244244import { getPresetManager, initPresetManager } from './scripts/preset-manager.js';
245245import { MacrosParser, evaluateMacros, getLastMessageId, initMacros } from './scripts/macros.js';
246246import { currentUser, setUserControls } from './scripts/user.js';
247247import { 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
44944494 instruction: main_api !== 'openai' && power_user.sysprompt.enabled ? substituteParams(power_user.prefer_character_prompt && system ? system : power_user.sysprompt.content) : '',
44954495 userPersona: (power_user.persona_description_position == persona_description_positions.IN_PROMPT ? (persona || '') : ''),
44964496 tokenizer: getFriendlyTokenizerName(main_api).tokenizerName || '',
4497+ presetName: getPresetManager()?.getSelectedPresetName() || '',
44974498 };
44984499
44994500 //console.log(additionalPromptStuff);
@@ -5160,6 +5161,7 @@ export async function itemizedParams(itemizedPrompts, thisPromptSet, incomingMes
51605161 dataBankVectorsStringTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].dataBankVectorsString),
51615162 modelUsed: chat[incomingMesId]?.extra?.model,
51625163 apiUsed: chat[incomingMesId]?.extra?.api,
5164+ presetName: itemizedPrompts[thisPromptSet].presetName || t`(Unknown)`,
51635165 };
51645166
51655167 const getFriendlyName = (value) => $(`#rm_api_block select option[value="${value}"]`).first().text() || value;
@@ -7752,25 +7754,31 @@ export async function saveChatConditional() {
77527754 }
77537755}
77547756
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+ */
77607762 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', {
77647766 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- },
77737770 });
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+ }
77747782}
77757783
77767784function updateViewMessageIds(startFromZero = false) {
@@ -10827,13 +10835,13 @@ jQuery(async function () {
1082710835 });
1082810836
1082910837 $('#chat_import_file').on('change', async function (e) {
1083010838 varconst file = e.target.files[0];
1083110839
1083210840 if (!file) {
1083310841 return;
1083410842 }
1083510843
1083610844 varconst ext = file.name.match(/\.(\w+)$/);
1083710845 if (
1083810846 !ext ||
1083910847 (ext[1].toLowerCase() != 'json' && ext[1].toLowerCase() != 'jsonl')
@@ -10846,17 +10854,17 @@ jQuery(async function () {
1084610854 return;
1084710855 }
1084810856
1084910857 varconst format = ext[1].toLowerCase();
1085010858 $('#chat_import_file_type').val(format);
1085110859
1085210860 varconst formData = new FormData($('#form_import_chat').get(0));
1085310861 formData.append('user_name', name1);
1085410862 $('#select_chat_div').html('');
1085510863
1085610864 if (selected_group) {
1085710865 await importGroupChat(formData, e.originalEvent.target);
1085810866 } else {
1085910867 await importCharacterChat(formData, e.originalEvent.target);
1086010868 }
1086110869 });
1086210870
public/scripts/group-chats.js+31 -25
@@ -1863,32 +1863,38 @@ export async function deleteGroupChat(groupId, chatId) {
18631863 }
18641864}
18651865
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+ */
18711871 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', {
18751875 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- },
18911879 });
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+ }
18921898}
18931899
18941900export async function saveGroupBookmarkChat(groupId, name, metadata, mesId) {
public/scripts/openai.js+2 -1
@@ -1812,6 +1812,7 @@ async function sendOpenAIRequest(type, messages, signal) {
18121812 const isPerplexity = oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY;
18131813 const isGroq = oai_settings.chat_completion_source == chat_completion_sources.GROQ;
18141814 const is01AI = oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI;
1815+ const isNano = oai_settings.chat_completion_source == chat_completion_sources.NANOGPT;
18151816 const isTextCompletion = isOAI && textCompletionModels.includes(oai_settings.openai_model);
18161817 const isQuiet = type === 'quiet';
18171818 const isImpersonate = type === 'impersonate';
@@ -1971,7 +1972,7 @@ async function sendOpenAIRequest(type, messages, signal) {
19711972 delete generate_data.stop;
19721973 }
19731974
19741975 if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere || isNano) && oai_settings.seed >= 0) {
19751976 generate_data['seed'] = oai_settings.seed;
19761977 }
19771978
public/scripts/slash-commands.js+8 -8
@@ -269,7 +269,7 @@ export function initDefaultSlashCommands() {
269269 }),
270270 SlashCommandNamedArgument.fromProps({
271271 name: 'at',
272272 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.',
273273 typeList: [ARGUMENT_TYPE.NUMBER],
274274 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
275275 }),
@@ -325,7 +325,7 @@ export function initDefaultSlashCommands() {
325325 ),
326326 SlashCommandNamedArgument.fromProps({
327327 name: 'at',
328328 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.',
329329 typeList: [ARGUMENT_TYPE.NUMBER],
330330 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
331331 }),
@@ -388,7 +388,7 @@ export function initDefaultSlashCommands() {
388388 ),
389389 SlashCommandNamedArgument.fromProps({
390390 name: 'at',
391391 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.',
392392 typeList: [ARGUMENT_TYPE.NUMBER],
393393 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
394394 }),
@@ -606,7 +606,7 @@ export function initDefaultSlashCommands() {
606606 ),
607607 SlashCommandNamedArgument.fromProps({
608608 name: 'at',
609609 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.',
610610 typeList: [ARGUMENT_TYPE.NUMBER],
611611 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
612612 }),
@@ -3025,7 +3025,7 @@ async function sendUserMessageCallback(args, text) {
30253025 let insertAt = Number(args?.at);
30263026
30273027 // Convert possible depth parameter to index
30283028 if (!isNaN(insertAt) && (insertAt < 0 || Object.is(insertAt ===, Number(-0))) {
30293029 // Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7)
30303030 insertAt = chat.length + insertAt;
30313031 }
@@ -3399,7 +3399,7 @@ export async function sendMessageAs(args, text) {
33993399 let insertAt = Number(args.at);
34003400
34013401 // Convert possible depth parameter to index
34023402 if (!isNaN(insertAt) && (insertAt < 0 || Object.is(insertAt ===, Number(-0))) {
34033403 // Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7)
34043404 insertAt = chat.length + insertAt;
34053405 }
@@ -3453,7 +3453,7 @@ export async function sendNarratorMessage(args, text) {
34533453 let insertAt = Number(args.at);
34543454
34553455 // Convert possible depth parameter to index
34563456 if (!isNaN(insertAt) && (insertAt < 0 || Object.is(insertAt ===, Number(-0))) {
34573457 // Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7)
34583458 insertAt = chat.length + insertAt;
34593459 }
@@ -3542,7 +3542,7 @@ async function sendCommentMessage(args, text) {
35423542 let insertAt = Number(args.at);
35433543
35443544 // Convert possible depth parameter to index
35453545 if (!isNaN(insertAt) && (insertAt < 0 || Object.is(insertAt ===, Number(-0))) {
35463546 // Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7)
35473547 insertAt = chat.length + insertAt;
35483548 }
public/scripts/templates/itemizationChat.html+11 -2
@@ -4,8 +4,17 @@
44 <div id="copyPromptToClipboard" class="fa-solid fa-copy menu_button" title="Copy Prompt" data-i18n="[title]Copy Prompt"></div>
55 <div id="diffPrevPrompt" class="fa-solid fa-code-compare menu_button" title="Show Prompt Differences" data-i18n="[title]Show Prompt Differences"></div>
66</h3>
7-API/Model Used: {{mainApiFriendlyName}} {{#if apiUsed}}({{apiUsed}}){{/if}} {{#if modelUsed}}&ndash; {{modelUsed}}{{/if}}<br>
7+<div>
8-Tokenizer: {{selectedTokenizer}}<br>
8+ <div>
9+ API/Model: {{mainApiFriendlyName}} {{#if apiUsed}}({{apiUsed}}){{/if}} {{#if modelUsed}}&ndash; {{modelUsed}}{{/if}}
10+ </div>
11+ <div>
12+ <small>Preset: {{presetName}}</small>
13+ <span>|</span>
14+ <small>Tokenizer: {{selectedTokenizer}}</small>
15+ </div>
16+</div>
17+
918<span class="tokenItemizingSubclass">
1019 Only the white numbers really matter. All numbers are estimates.
1120 Grey color items may not have been included in the context due to certain prompt format settings.
public/scripts/templates/itemizationText.html+11 -2
@@ -4,8 +4,17 @@
44 <div id="copyPromptToClipboard" class="fa-solid fa-copy menu_button" title="Copy Prompt" data-i18n="[title]Copy Prompt"></div>
55 <div id="diffPrevPrompt" class="fa-solid fa-code-compare menu_button" title="Show Prompt Differences" data-i18n="[title]Show Prompt Differences"></div>
66</h3>
7-API/Model Used: {{mainApiFriendlyName}} {{#if apiUsed}}({{apiUsed}}){{/if}} {{#if modelUsed}}&ndash; {{modelUsed}}{{/if}}<br>
7+<div>
8-Tokenizer: {{selectedTokenizer}}<br>
8+ <div>
9+ API/Model: {{mainApiFriendlyName}} {{#if apiUsed}}({{apiUsed}}){{/if}} {{#if modelUsed}}&ndash; {{modelUsed}}{{/if}}
10+ </div>
11+ <div>
12+ <small>Preset: {{presetName}}</small>
13+ <span>|</span>
14+ <small>Tokenizer: {{selectedTokenizer}}</small>
15+ </div>
16+</div>
17+
918<span class="tokenItemizingSubclass">
1019 Only the white numbers really matter. All numbers are estimates.
1120 Grey color items may not have been included in the context due to certain prompt format settings.
public/scripts/textgen-settings.js+3 -0
@@ -1340,6 +1340,9 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
13401340 'dry_sequence_breakers': sequenceBreakers,
13411341 };
13421342 params = Object.assign(params, llamaCppParams);
1343+ if (!Array.isArray(sequenceBreakers) || sequenceBreakers.length === 0) {
1344+ delete params.dry_sequence_breakers;
1345+ }
13431346 }
13441347
13451348 eventSource.emitAndWait(event_types.TEXT_COMPLETION_SETTINGS_READY, params);
src/endpoints/chats.js+74 -27
@@ -191,6 +191,44 @@ function importCAIChat(userName, characterName, jsonData) {
191191}
192192
193193/**
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+/**
194232 * Flattens `msg` and `swipes` data from Chub Chat format.
195233 * Only changes enough to make it compatible with the standard chat serialization format.
196234 * @param {string} userName User name
@@ -413,7 +451,7 @@ router.post('/import', urlencodedParser, function (request, response) {
413451 const format = request.body.file_type;
414452 const avatarUrl = (request.body.avatar_url).replace('.png', '');
415453 const characterName = request.body.character_name;
416454 const userName = request.body.user_name || 'YouUser';
417455
418456 if (!request.file) {
419457 return response.sendStatus(400);
@@ -426,33 +464,38 @@ router.post('/import', urlencodedParser, function (request, response) {
426464 if (format === 'json') {
427465 fs.unlinkSync(pathToUpload);
428466 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);
448485 const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`;
449486 const filePath = path.join(request.user.directories.chats, avatarUrl, fileName);
450487 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);
452494 } else {
453- console.log('Incorrect chat format .json');
495+ handleChat(chat);
454- return response.send({ error: true });
455496 }
497+
498+ return response.send({ res: true });
456499 }
457500
458501 if (format === 'jsonl') {
@@ -561,10 +604,14 @@ router.post('/search', jsonParser, function (request, response) {
561604
562605 let targetGroup;
563606 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);
568615 }
569616 }
570617