Add Kobold Lite chats import

This commit is contained in:
Cohee
2024-12-04 00:44:50 +02:00
parent 8de1d26eaa
commit 79700fd983
3 changed files with 129 additions and 74 deletions

View File

@@ -7754,25 +7754,31 @@ export async function saveChatConditional() {
} }
} }
async function importCharacterChat(formData) { /**
await jQuery.ajax({ * Saves the chat to the server.
type: 'POST', * @param {FormData} formData Form data to send to the server.
url: '/api/chats/import', * @param {EventTarget} eventTarget Event target to trigger the event on.
data: formData, */
beforeSend: function () { async function importCharacterChat(formData, eventTarget) {
}, const headers = getRequestHeaders();
cache: false, delete headers['Content-Type'];
contentType: false, const fetchResult = await fetch('/api/chats/import', {
processData: false, method: 'POST',
success: async function (data) { body: formData,
if (data.res) { headers: headers,
await displayPastChats(); cache: 'no-cache',
}
},
error: function () {
$('#create_button').removeAttr('disabled');
},
}); });
if (fetchResult.ok) {
const data = await fetchResult.json();
if (data.res) {
await displayPastChats();
}
}
if (eventTarget instanceof HTMLInputElement) {
eventTarget.value = '';
}
} }
function updateViewMessageIds(startFromZero = false) { function updateViewMessageIds(startFromZero = false) {
@@ -10829,13 +10835,13 @@ jQuery(async function () {
}); });
$('#chat_import_file').on('change', async function (e) { $('#chat_import_file').on('change', async function (e) {
var file = e.target.files[0]; const file = e.target.files[0];
if (!file) { if (!file) {
return; return;
} }
var ext = file.name.match(/\.(\w+)$/); const ext = file.name.match(/\.(\w+)$/);
if ( if (
!ext || !ext ||
(ext[1].toLowerCase() != 'json' && ext[1].toLowerCase() != 'jsonl') (ext[1].toLowerCase() != 'json' && ext[1].toLowerCase() != 'jsonl')
@@ -10848,17 +10854,17 @@ jQuery(async function () {
return; return;
} }
var format = ext[1].toLowerCase(); const format = ext[1].toLowerCase();
$('#chat_import_file_type').val(format); $('#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); formData.append('user_name', name1);
$('#select_chat_div').html(''); $('#select_chat_div').html('');
if (selected_group) { if (selected_group) {
await importGroupChat(formData); await importGroupChat(formData, e.originalEvent.target);
} else { } else {
await importCharacterChat(formData); await importCharacterChat(formData, e.originalEvent.target);
} }
}); });

View File

@@ -1863,32 +1863,38 @@ export async function deleteGroupChat(groupId, chatId) {
} }
} }
export async function importGroupChat(formData) { /**
await jQuery.ajax({ * Imports a group chat from a file and adds it to the group.
type: 'POST', * @param {FormData} formData Form data to send to the server
url: '/api/chats/group/import', * @param {EventTarget} eventTarget Element that triggered the import
data: formData, */
beforeSend: function () { export async function importGroupChat(formData, eventTarget) {
}, const headers = getRequestHeaders();
cache: false, delete headers['Content-Type'];
contentType: false, const fetchResult = await fetch('/api/chats/group/import', {
processData: false, method: 'POST',
success: async function (data) { headers: headers,
if (data.res) { body: formData,
const chatId = data.res; cache: 'no-cache',
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');
},
}); });
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) { export async function saveGroupBookmarkChat(groupId, name, metadata, mesId) {

View File

@@ -190,6 +190,44 @@ function importCAIChat(userName, characterName, jsonData) {
return newChats; 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. * Flattens `msg` and `swipes` data from Chub Chat format.
* Only changes enough to make it compatible with the standard chat serialization 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 format = request.body.file_type;
const avatarUrl = (request.body.avatar_url).replace('.png', ''); const avatarUrl = (request.body.avatar_url).replace('.png', '');
const characterName = request.body.character_name; const characterName = request.body.character_name;
const userName = request.body.user_name || 'You'; const userName = request.body.user_name || 'User';
if (!request.file) { if (!request.file) {
return response.sendStatus(400); return response.sendStatus(400);
@@ -426,33 +464,38 @@ router.post('/import', urlencodedParser, function (request, response) {
if (format === 'json') { if (format === 'json') {
fs.unlinkSync(pathToUpload); fs.unlinkSync(pathToUpload);
const jsonData = JSON.parse(data); const jsonData = JSON.parse(data);
if (jsonData.histories !== undefined) {
// CAI Tools format /** @type {function(string, string, object): string|string[]} */
const chats = importCAIChat(userName, characterName, jsonData); let importFunc;
for (const chat of chats) {
const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`; if (jsonData.savedsettings !== undefined) { // Kobold Lite format
const filePath = path.join(request.user.directories.chats, avatarUrl, fileName); importFunc = importKoboldLiteChat;
writeFileAtomicSync(filePath, chat, 'utf8'); } else if (jsonData.histories !== undefined) { // CAI Tools format
} importFunc = importCAIChat;
return response.send({ res: true }); } else if (Array.isArray(jsonData.data_visible)) { // oobabooga's format
} else if (Array.isArray(jsonData.data_visible)) { importFunc = importOobaChat;
// oobabooga's format } else if (Array.isArray(jsonData.messages)) { // Agnai's format
const chat = importOobaChat(userName, characterName, jsonData); importFunc = importAgnaiChat;
const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`; } else { // Unknown format
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 {
console.log('Incorrect chat format .json'); console.log('Incorrect chat format .json');
return response.send({ error: true }); 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') { if (format === 'jsonl') {