Minimal support for importing chat histories in Chub format Fix crasher in script.js:displayChats if user has directly put a Chub chat file into their user data Let eslint tidy a few things

a2d9526cdfa73d2e4eac0fe662479fe77c5e0e8f

ceruleandeep <deep@cerulean.navy>

2 files changed, +62 -21Showing whitespace changes
public/script.js+3 -5
@@ -6923,15 +6923,13 @@ export async function displayPastChats() {
69236923 }
69246924 // Check whether `text` {string} includes all of the `fragments` {string[]}.
69256925 function matchFragments(fragments, text) {
69266926 if (!text || !text.toLowerCase) {return false;
6927- return false;
6927+ return fragments.every(item => text.toLowerCase().includes(item));
6928- }
6929- return fragments.every(item => text.includes(item));
69306928 }
69316929 const fragments = makeQueryFragments(searchQuery);
69326930 // At least one chat message must match *all* the fragments.
69336931 // Currently, this doesn't match if the fragment matches are distributed across several chat messages.
69346932 return chatContent && Object.values(chatContent).some(message => matchFragments(fragments, message?.mes?.toLowerCase()));
69356933 });
69366934
69376935 console.debug(filteredData);
src/endpoints/chats.js+59 -16
@@ -92,8 +92,7 @@ function importOobaChat(userName, characterName, jsonData) {
9292 }
9393 }
9494
9595 const chatContent =return chat.map(obj => JSON.stringify(obj)).join('\n');
96- return chatContent;
9796}
9897
9998/**
@@ -121,8 +120,7 @@ function importAgnaiChat(userName, characterName, jsonData) {
121120 });
122121 }
123122
124123 const chatContent =return chat.map(obj => JSON.stringify(obj)).join('\n');
125- return chatContent;
126124}
127125
128126/**
@@ -159,6 +157,37 @@ function importCAIChat(userName, characterName, jsonData) {
159157 return newChats;
160158}
161159
160+/**
161+ * Flattens `msg` and `swipes` data from Chub Chat format.
162+ * Only changes enough to make it compatible with the standard chat serialization format.
163+ * @param {string} userName User name
164+ * @param {string} characterName Character name
165+ * @param {string[]} lines serialised JSONL data
166+ * @returns {string} Converted data
167+ */
168+function flattenChubChat(userName, characterName, lines) {
169+ function flattenSwipe(swipe) {
170+ return swipe.message ? swipe.message : swipe;
171+ }
172+
173+ function convert(line) {
174+ const lineData = tryParse(line);
175+ if (!lineData) return line;
176+
177+ if (lineData.mes && lineData.mes.message) {
178+ lineData.mes = lineData?.mes.message;
179+ }
180+
181+ if (lineData?.swipes && Array.isArray(lineData.swipes)) {
182+ lineData.swipes = lineData.swipes.map(swipe => flattenSwipe(swipe));
183+ }
184+
185+ return JSON.stringify(lineData);
186+ }
187+
188+ return (lines ?? []).map(convert).join('\n');
189+}
190+
162191const router = express.Router();
163192
164193router.post('/save', jsonParser, function (request, response) {
@@ -273,7 +302,7 @@ router.post('/export', jsonParser, async function (request, response) {
273302 }
274303 try {
275304 // Short path for JSONL files
276305 if (request.body.format === 'jsonl') {
277306 try {
278307 const rawFile = fs.readFileSync(filename, 'utf8');
279308 const successMessage = {
@@ -283,8 +312,7 @@ router.post('/export', jsonParser, async function (request, response) {
283312
284313 console.log(`Chat exported as ${exportfilename}`);
285314 return response.status(200).json(successMessage);
286- }
315+ } catch (err) {
287- catch (err) {
288316 console.error(err);
289317 const errorMessage = {
290318 message: `Could not read JSONL file to export. Source chat file: ${filename}.`,
@@ -319,8 +347,7 @@ router.post('/export', jsonParser, async function (request, response) {
319347 console.log(`Chat exported as ${exportfilename}`);
320348 return response.status(200).json(successMessage);
321349 });
322- }
350+ } catch (err) {
323- catch (err) {
324351 console.log('chat export failed.');
325352 console.log(err);
326353 return response.sendStatus(400);
@@ -396,20 +423,36 @@ router.post('/import', urlencodedParser, function (request, response) {
396423 }
397424
398425 if (format === 'jsonl') {
399426 constlet linelines = data.split('\n')[0];
427+ const header = lines[0];
428+
429+ const jsonData = JSON.parse(header);
430+
431+ if (!(jsonData.user_name !== undefined || jsonData.name !== undefined)) {
432+ console.log('Incorrect chat format .jsonl');
433+ return response.send({ error: true });
434+ }
400435
401- const jsonData = JSON.parse(line);
436+ // Do a tiny bit of work to import Chub Chat data
437+ // Processing the entire file is so fast that it's not worth checking if it's a Chub chat first
438+ let flattenedChat;
439+ try {
440+ // flattening is unlikely to break, but it's not worth failing to
441+ // import normal chats in an attempt to import a Chub chat
442+ flattenedChat = flattenChubChat(userName, characterName, lines);
443+ } catch (error) {
444+ console.warn('Failed to flatten Chub Chat data: ', error);
445+ }
402446
403- if (jsonData.user_name !== undefined || jsonData.name !== undefined) {
404447 const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`;
405448 const filePath = path.join(request.user.directories.chats, avatarUrl, fileName);
449+ if (flattenedChat !== data) {
450+ writeFileAtomicSync(filePath, flattenedChat, 'utf8');
451+ } else {
406452 fs.copyFileSync(pathToUpload, filePath);
453+ }
407454 fs.unlinkSync(pathToUpload);
408455 response.send({ res: true });
409- } else {
410- console.log('Incorrect chat format .jsonl');
411- return response.send({ error: true });
412- }
413456 }
414457 } catch (error) {
415458 console.error(error);