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 | // Check whether `text` {string} includes all of the `fragments` {string[]}. | 6924 | // Check whether `text` {string} includes all of the `fragments` {string[]}. |
| 6925 | function matchFragments(fragments, text) { | 6925 | function matchFragments(fragments, text) { |
| 6926 | if (!text) { | 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 | const fragments = makeQueryFragments(searchQuery); | 6929 | const fragments = makeQueryFragments(searchQuery); |
| 6932 | // At least one chat message must match *all* the fragments. | 6930 | // At least one chat message must match *all* the fragments. |
| 6933 | // Currently, this doesn't match if the fragment matches are distributed across several chat messages. | 6931 | // Currently, this doesn't match if the fragment matches are distributed across several chat messages. |
| 6934 | return chatContent && Object.values(chatContent).some(message => matchFragments(fragments, message?.mes?.toLowerCase())); | 6932 | return chatContent && Object.values(chatContent).some(message => matchFragments(fragments, message?.mes)); |
| 6935 | }); | 6933 | }); |
| 6936 | 6934 | ||
| 6937 | console.debug(filteredData); | 6935 | console.debug(filteredData); |
| @@ -92,8 +92,7 @@ function importOobaChat(userName, characterName, jsonData) { | |||
| 92 | } | 92 | } |
| 93 | } | 93 | } |
| 94 | 94 | ||
| 95 | const chatContent = chat.map(obj => JSON.stringify(obj)).join('\n'); | 95 | 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 | const chatContent = chat.map(obj => JSON.stringify(obj)).join('\n'); | 123 | 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 | return newChats; | 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 | const router = express.Router(); | 191 | const router = express.Router(); |
| 163 | 192 | ||
| 164 | router.post('/save', jsonParser, function (request, response) { | 193 | router.post('/save', jsonParser, function (request, response) { |
| @@ -273,7 +302,7 @@ router.post('/export', jsonParser, async function (request, response) { | |||
| 273 | } | 302 | } |
| 274 | try { | 303 | try { |
| 275 | // Short path for JSONL files | 304 | // Short path for JSONL files |
| 276 | if (request.body.format == 'jsonl') { | 305 | if (request.body.format === 'jsonl') { |
| 277 | try { | 306 | try { |
| 278 | const rawFile = fs.readFileSync(filename, 'utf8'); | 307 | const rawFile = fs.readFileSync(filename, 'utf8'); |
| 279 | const successMessage = { | 308 | const successMessage = { |
| @@ -283,8 +312,7 @@ router.post('/export', jsonParser, async function (request, response) { | |||
| 283 | 312 | ||
| 284 | console.log(`Chat exported as ${exportfilename}`); | 313 | console.log(`Chat exported as ${exportfilename}`); |
| 285 | return response.status(200).json(successMessage); | 314 | return response.status(200).json(successMessage); |
| 286 | } | 315 | } catch (err) { |
| 287 | catch (err) { | ||
| 288 | console.error(err); | 316 | console.error(err); |
| 289 | const errorMessage = { | 317 | const errorMessage = { |
| 290 | message: `Could not read JSONL file to export. Source chat file: ${filename}.`, | 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 | console.log(`Chat exported as ${exportfilename}`); | 347 | console.log(`Chat exported as ${exportfilename}`); |
| 320 | return response.status(200).json(successMessage); | 348 | return response.status(200).json(successMessage); |
| 321 | }); | 349 | }); |
| 322 | } | 350 | } catch (err) { |
| 323 | catch (err) { | ||
| 324 | console.log('chat export failed.'); | 351 | console.log('chat export failed.'); |
| 325 | console.log(err); | 352 | console.log(err); |
| 326 | return response.sendStatus(400); | 353 | return response.sendStatus(400); |
| @@ -396,20 +423,36 @@ router.post('/import', urlencodedParser, function (request, response) { | |||
| 396 | } | 423 | } |
| 397 | 424 | ||
| 398 | if (format === 'jsonl') { | 425 | if (format === 'jsonl') { |
| 399 | const line = data.split('\n')[0]; | 426 | let lines = data.split('\n'); |
| 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 | const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`; | 447 | const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`; |
| 405 | const filePath = path.join(request.user.directories.chats, avatarUrl, fileName); | 448 | const filePath = path.join(request.user.directories.chats, avatarUrl, fileName); |
| 449 | if (flattenedChat !== data) { | ||
| 450 | writeFileAtomicSync(filePath, flattenedChat, 'utf8'); | ||
| 451 | } else { | ||
| 406 | fs.copyFileSync(pathToUpload, filePath); | 452 | fs.copyFileSync(pathToUpload, filePath); |
| 453 | } | ||
| 407 | fs.unlinkSync(pathToUpload); | 454 | fs.unlinkSync(pathToUpload); |
| 408 | response.send({ res: true }); | 455 | response.send({ res: true }); |
| 409 | } else { | ||
| 410 | console.log('Incorrect chat format .jsonl'); | ||
| 411 | return response.send({ error: true }); | ||
| 412 | } | ||
| 413 | } | 456 | } |
| 414 | } catch (error) { | 457 | } catch (error) { |
| 415 | console.error(error); | 458 | console.error(error); |