Add Kobold Lite chats import
| @@ -7754,25 +7754,31 @@ export async function saveChatConditional() { | |||
| 7754 | } | 7754 | } |
| 7755 | } | 7755 | } |
| 7756 | 7756 | ||
| 7757 | async function importCharacterChat(formData) { | 7757 | /** |
| 7758 | await jQuery.ajax({ | 7758 | * Saves the chat to the server. |
| 7759 | type: 'POST', | 7759 | * @param {FormData} formData Form data to send to the server. |
| 7760 | url: '/api/chats/import', | 7760 | * @param {EventTarget} eventTarget Event target to trigger the event on. |
| 7761 | data: formData, | 7761 | */ |
| 7762 | beforeSend: function () { | 7762 | async function importCharacterChat(formData, eventTarget) { |
| 7763 | }, | 7763 | const headers = getRequestHeaders(); |
| 7764 | cache: false, | 7764 | delete headers['Content-Type']; |
| 7765 | contentType: false, | 7765 | const fetchResult = await fetch('/api/chats/import', { |
| 7766 | processData: false, | 7766 | method: 'POST', |
| 7767 | success: async function (data) { | 7767 | body: formData, |
| 7768 | if (data.res) { | 7768 | headers: headers, |
| 7769 | await displayPastChats(); | 7769 | cache: 'no-cache', |
| 7770 | } | ||
| 7771 | }, | ||
| 7772 | error: function () { | ||
| 7773 | $('#create_button').removeAttr('disabled'); | ||
| 7774 | }, | ||
| 7775 | }); | 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 | } | ||
| 7776 | } | 7782 | } |
| 7777 | 7783 | ||
| 7778 | function updateViewMessageIds(startFromZero = false) { | 7784 | function updateViewMessageIds(startFromZero = false) { |
| @@ -10829,13 +10835,13 @@ jQuery(async function () { | |||
| 10829 | }); | 10835 | }); |
| 10830 | 10836 | ||
| 10831 | $('#chat_import_file').on('change', async function (e) { | 10837 | $('#chat_import_file').on('change', async function (e) { |
| 10832 | var file = e.target.files[0]; | 10838 | const file = e.target.files[0]; |
| 10833 | 10839 | ||
| 10834 | if (!file) { | 10840 | if (!file) { |
| 10835 | return; | 10841 | return; |
| 10836 | } | 10842 | } |
| 10837 | 10843 | ||
| 10838 | var ext = file.name.match(/\.(\w+)$/); | 10844 | const ext = file.name.match(/\.(\w+)$/); |
| 10839 | if ( | 10845 | if ( |
| 10840 | !ext || | 10846 | !ext || |
| 10841 | (ext[1].toLowerCase() != 'json' && ext[1].toLowerCase() != 'jsonl') | 10847 | (ext[1].toLowerCase() != 'json' && ext[1].toLowerCase() != 'jsonl') |
| @@ -10848,17 +10854,17 @@ jQuery(async function () { | |||
| 10848 | return; | 10854 | return; |
| 10849 | } | 10855 | } |
| 10850 | 10856 | ||
| 10851 | var format = ext[1].toLowerCase(); | 10857 | const format = ext[1].toLowerCase(); |
| 10852 | $('#chat_import_file_type').val(format); | 10858 | $('#chat_import_file_type').val(format); |
| 10853 | 10859 | ||
| 10854 | var formData = new FormData($('#form_import_chat').get(0)); | 10860 | const formData = new FormData($('#form_import_chat').get(0)); |
| 10855 | formData.append('user_name', name1); | 10861 | formData.append('user_name', name1); |
| 10856 | $('#select_chat_div').html(''); | 10862 | $('#select_chat_div').html(''); |
| 10857 | 10863 | ||
| 10858 | if (selected_group) { | 10864 | if (selected_group) { |
| 10859 | await importGroupChat(formData); | 10865 | await importGroupChat(formData, e.originalEvent.target); |
| 10860 | } else { | 10866 | } else { |
| 10861 | await importCharacterChat(formData); | 10867 | await importCharacterChat(formData, e.originalEvent.target); |
| 10862 | } | 10868 | } |
| 10863 | }); | 10869 | }); |
| 10864 | 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 | beforeSend: function () { | 1871 | 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 | processData: false, | 1875 | method: '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 | export async function saveGroupBookmarkChat(groupId, name, metadata, mesId) { | 1900 | export async function saveGroupBookmarkChat(groupId, name, metadata, mesId) { |
| @@ -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 | * Flattens `msg` and `swipes` data from Chub Chat format. | 232 | * Flattens `msg` and `swipes` data from Chub Chat format. |
| 195 | * Only changes enough to make it compatible with the standard chat serialization format. | 233 | * Only changes enough to make it compatible with the standard chat serialization format. |
| 196 | * @param {string} userName User name | 234 | * @param {string} userName User name |
| @@ -413,7 +451,7 @@ router.post('/import', urlencodedParser, function (request, response) { | |||
| 413 | const format = request.body.file_type; | 451 | const format = request.body.file_type; |
| 414 | const avatarUrl = (request.body.avatar_url).replace('.png', ''); | 452 | const avatarUrl = (request.body.avatar_url).replace('.png', ''); |
| 415 | const characterName = request.body.character_name; | 453 | const characterName = request.body.character_name; |
| 416 | const userName = request.body.user_name || 'You'; | 454 | const userName = request.body.user_name || 'User'; |
| 417 | 455 | ||
| 418 | if (!request.file) { | 456 | if (!request.file) { |
| 419 | return response.sendStatus(400); | 457 | return response.sendStatus(400); |
| @@ -426,33 +464,38 @@ router.post('/import', urlencodedParser, function (request, response) { | |||
| 426 | if (format === 'json') { | 464 | if (format === 'json') { |
| 427 | fs.unlinkSync(pathToUpload); | 465 | fs.unlinkSync(pathToUpload); |
| 428 | const jsonData = JSON.parse(data); | 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 | const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`; | 485 | const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`; |
| 449 | const filePath = path.join(request.user.directories.chats, avatarUrl, fileName); | 486 | const filePath = path.join(request.user.directories.chats, avatarUrl, fileName); |
| 450 | writeFileAtomicSync(filePath, chat, 'utf8'); | 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 | } else { | 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 | if (format === 'jsonl') { | 501 | if (format === 'jsonl') { |