Merge pull request #2875 from ceruleandeep/feature/handleChubChatData Fix crasher in script.js:displayChats, add minimal support for importing chat histories in Chub format
Signed| @@ -6923,15 +6923,13 @@ export async function displayPastChats() { | ||
| 6923 | 6923 | } |
| 6924 | 6924 | // Check whether `text` {string} includes all of the `fragments` {string[]}. |
| 6925 | 6925 | function matchFragments(fragments, text) { |
| 6926 | 6926 | 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)); | |
| 6930 | 6928 | } |
| 6931 | 6929 | const fragments = makeQueryFragments(searchQuery); |
| 6932 | 6930 | // At least one chat message must match *all* the fragments. |
| 6933 | 6931 | // Currently, this doesn't match if the fragment matches are distributed across several chat messages. |
| 6934 | 6932 | return chatContent && Object.values(chatContent).some(message => matchFragments(fragments, message?.mes?.toLowerCase())); |
| 6935 | 6933 | }); |
| 6936 | 6934 | |
| 6937 | 6935 | console.debug(filteredData); |
| @@ -92,8 +92,7 @@ function importOobaChat(userName, characterName, jsonData) { | ||
| 92 | 92 | } |
| 93 | 93 | } |
| 94 | 94 | |
| 95 | 95 | const chatContent =return chat.map(obj => JSON.stringify(obj)).join('\n'); |
| 96 | - return chatContent; | |
| 97 | 96 | } |
| 98 | 97 | |
| 99 | 98 | /** |
| @@ -121,8 +120,7 @@ function importAgnaiChat(userName, characterName, jsonData) { | ||
| 121 | 120 | }); |
| 122 | 121 | } |
| 123 | 122 | |
| 124 | 123 | const chatContent =return chat.map(obj => JSON.stringify(obj)).join('\n'); |
| 125 | - return chatContent; | |
| 126 | 124 | } |
| 127 | 125 | |
| 128 | 126 | /** |
| @@ -159,6 +157,37 @@ function importCAIChat(userName, characterName, jsonData) { | ||
| 159 | 157 | return newChats; |
| 160 | 158 | } |
| 161 | 159 | |
| 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 | + | |
| 162 | 191 | const router = express.Router(); |
| 163 | 192 | |
| 164 | 193 | router.post('/save', jsonParser, function (request, response) { |
| @@ -273,7 +302,7 @@ router.post('/export', jsonParser, async function (request, response) { | ||
| 273 | 302 | } |
| 274 | 303 | try { |
| 275 | 304 | // Short path for JSONL files |
| 276 | 305 | if (request.body.format === 'jsonl') { |
| 277 | 306 | try { |
| 278 | 307 | const rawFile = fs.readFileSync(filename, 'utf8'); |
| 279 | 308 | const successMessage = { |
| @@ -283,8 +312,7 @@ router.post('/export', jsonParser, async function (request, response) { | ||
| 283 | 312 | |
| 284 | 313 | console.log(`Chat exported as ${exportfilename}`); |
| 285 | 314 | return response.status(200).json(successMessage); |
| 286 | - } | |
| 315 | + } catch (err) { | |
| 287 | - catch (err) { | |
| 288 | 316 | console.error(err); |
| 289 | 317 | const errorMessage = { |
| 290 | 318 | message: `Could not read JSONL file to export. Source chat file: ${filename}.`, |
| @@ -319,8 +347,7 @@ router.post('/export', jsonParser, async function (request, response) { | ||
| 319 | 347 | console.log(`Chat exported as ${exportfilename}`); |
| 320 | 348 | return response.status(200).json(successMessage); |
| 321 | 349 | }); |
| 322 | - } | |
| 350 | + } catch (err) { | |
| 323 | - catch (err) { | |
| 324 | 351 | console.log('chat export failed.'); |
| 325 | 352 | console.log(err); |
| 326 | 353 | return response.sendStatus(400); |
| @@ -396,20 +423,36 @@ router.post('/import', urlencodedParser, function (request, response) { | ||
| 396 | 423 | } |
| 397 | 424 | |
| 398 | 425 | if (format === 'jsonl') { |
| 399 | 426 | 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 | + } | |
| 400 | 435 | |
| 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 | + } | |
| 402 | 446 | |
| 403 | - if (jsonData.user_name !== undefined || jsonData.name !== undefined) { | |
| 404 | 447 | const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`; |
| 405 | 448 | const filePath = path.join(request.user.directories.chats, avatarUrl, fileName); |
| 449 | + if (flattenedChat !== data) { | |
| 450 | + writeFileAtomicSync(filePath, flattenedChat, 'utf8'); | |
| 451 | + } else { | |
| 406 | 452 | fs.copyFileSync(pathToUpload, filePath); |
| 453 | + } | |
| 407 | 454 | fs.unlinkSync(pathToUpload); |
| 408 | 455 | response.send({ res: true }); |
| 409 | - } else { | |
| 410 | - console.log('Incorrect chat format .jsonl'); | |
| 411 | - return response.send({ error: true }); | |
| 412 | - } | |
| 413 | 456 | } |
| 414 | 457 | } catch (error) { |
| 415 | 458 | console.error(error); |