Merge pull request #2512 from kwaroran/ccv3-write CCv3 Partial Implementation
Signed| @@ -464,6 +464,7 @@ export function evaluateMacros(content, env) { | |||
| 464 | content = content.replace(/{{firstIncludedMessageId}}/gi, () => String(getFirstIncludedMessageId() ?? '')); | 464 | content = content.replace(/{{firstIncludedMessageId}}/gi, () => String(getFirstIncludedMessageId() ?? '')); |
| 465 | content = content.replace(/{{lastSwipeId}}/gi, () => String(getLastSwipeId() ?? '')); | 465 | content = content.replace(/{{lastSwipeId}}/gi, () => String(getLastSwipeId() ?? '')); |
| 466 | content = content.replace(/{{currentSwipeId}}/gi, () => String(getCurrentSwipeId() ?? '')); | 466 | content = content.replace(/{{currentSwipeId}}/gi, () => String(getCurrentSwipeId() ?? '')); |
| 467 | content = content.replace(/{{reverse\:(.+?)}}/gi, (_, str) => Array.from(str).reverse().join('')); | ||
| 467 | 468 | ||
| 468 | content = content.replace(/\{\{\/\/([\s\S]*?)\}\}/gm, ''); | 469 | content = content.replace(/\{\{\/\/([\s\S]*?)\}\}/gm, ''); |
| 469 | 470 | ||
| @@ -28,6 +28,7 @@ | |||
| 28 | <li><tt>{{firstIncludedMessageId}}</tt> – <span data-i18n="help_macros_22">the ID of the first message included in the context. Requires generation to be ran at least once in the current session.</span></li> | 28 | <li><tt>{{firstIncludedMessageId}}</tt> – <span data-i18n="help_macros_22">the ID of the first message included in the context. Requires generation to be ran at least once in the current session.</span></li> |
| 29 | <li><tt>{{currentSwipeId}}</tt> – <span data-i18n="help_macros_23">the 1-based ID of the current swipe in the last chat message. Empty string if the last message is user or prompt-hidden.</span></li> | 29 | <li><tt>{{currentSwipeId}}</tt> – <span data-i18n="help_macros_23">the 1-based ID of the current swipe in the last chat message. Empty string if the last message is user or prompt-hidden.</span></li> |
| 30 | <li><tt>{{lastSwipeId}}</tt> – <span data-i18n="help_macros_24">the number of swipes in the last chat message. Empty string if the last message is user or prompt-hidden.</span></li> | 30 | <li><tt>{{lastSwipeId}}</tt> – <span data-i18n="help_macros_24">the number of swipes in the last chat message. Empty string if the last message is user or prompt-hidden.</span></li> |
| 31 | <li><tt>{{reverse:(content)}}</tt> – <span data-i18n="help_macros_reverse">reverses the content of the macro.</span></li> | ||
| 31 | <li><tt>{{// (note)}}</tt> – <span data-i18n="help_macros_25">you can leave a note here, and the macro will be replaced with blank content. Not visible for the AI.</span></li> | 32 | <li><tt>{{// (note)}}</tt> – <span data-i18n="help_macros_25">you can leave a note here, and the macro will be replaced with blank content. Not visible for the AI.</span></li> |
| 32 | <li><tt>{{time}}</tt> – <span data-i18n="help_macros_26">the current time</span></li> | 33 | <li><tt>{{time}}</tt> – <span data-i18n="help_macros_26">the current time</span></li> |
| 33 | <li><tt>{{date}}</tt> – <span data-i18n="help_macros_27">the current date</span></li> | 34 | <li><tt>{{date}}</tt> – <span data-i18n="help_macros_27">the current date</span></li> |
| @@ -107,6 +107,7 @@ const METADATA_KEY = 'world_info'; | |||
| 107 | const DEFAULT_DEPTH = 4; | 107 | const DEFAULT_DEPTH = 4; |
| 108 | const DEFAULT_WEIGHT = 100; | 108 | const DEFAULT_WEIGHT = 100; |
| 109 | const MAX_SCAN_DEPTH = 1000; | 109 | const MAX_SCAN_DEPTH = 1000; |
| 110 | const KNOWN_DECORATORS = ['@@activate', '@@dont_activate']; | ||
| 110 | 111 | ||
| 111 | // Typedef area | 112 | // Typedef area |
| 112 | /** | 113 | /** |
| @@ -123,6 +124,7 @@ const MAX_SCAN_DEPTH = 1000; | |||
| 123 | * @property {number} [sticky] The sticky value of the entry | 124 | * @property {number} [sticky] The sticky value of the entry |
| 124 | * @property {number} [cooldown] The cooldown of the entry | 125 | * @property {number} [cooldown] The cooldown of the entry |
| 125 | * @property {number} [delay] The delay of the entry | 126 | * @property {number} [delay] The delay of the entry |
| 127 | * @property {string[]} [decorators] Array of decorators for the entry | ||
| 126 | */ | 128 | */ |
| 127 | 129 | ||
| 128 | /** | 130 | /** |
| @@ -3534,6 +3536,12 @@ export async function getSortedEntries() { | |||
| 3534 | // Chat lore always goes first | 3536 | // Chat lore always goes first |
| 3535 | entries = [...chatLore.sort(sortFn), ...entries]; | 3537 | entries = [...chatLore.sort(sortFn), ...entries]; |
| 3536 | 3538 | ||
| 3539 | // Parse decorators | ||
| 3540 | entries = entries.map((entry) => { | ||
| 3541 | const [decorators, content] = parseDecorators(entry.content); | ||
| 3542 | return { ...entry, decorators, content }; | ||
| 3543 | }); | ||
| 3544 | |||
| 3537 | console.debug(`[WI] Found ${entries.length} world lore entries. Sorted by strategy`, Object.entries(world_info_insertion_strategy).find((x) => x[1] === world_info_character_strategy)); | 3545 | console.debug(`[WI] Found ${entries.length} world lore entries. Sorted by strategy`, Object.entries(world_info_insertion_strategy).find((x) => x[1] === world_info_character_strategy)); |
| 3538 | 3546 | ||
| 3539 | // Need to deep clone the entries to avoid modifying the cached data | 3547 | // Need to deep clone the entries to avoid modifying the cached data |
| @@ -3545,6 +3553,62 @@ export async function getSortedEntries() { | |||
| 3545 | } | 3553 | } |
| 3546 | } | 3554 | } |
| 3547 | 3555 | ||
| 3556 | |||
| 3557 | /** | ||
| 3558 | * Parse decorators from worldinfo content | ||
| 3559 | * @param {string} content The content to parse | ||
| 3560 | * @returns {[string[],string]} The decorators found in the content and the content without decorators | ||
| 3561 | */ | ||
| 3562 | function parseDecorators(content) { | ||
| 3563 | /** | ||
| 3564 | * Check if the decorator is known | ||
| 3565 | * @param {string} data string to check | ||
| 3566 | * @returns {boolean} true if the decorator is known | ||
| 3567 | */ | ||
| 3568 | const isKnownDecorator = (data) => { | ||
| 3569 | if (data.startsWith('@@@')) { | ||
| 3570 | data = data.substring(1); | ||
| 3571 | } | ||
| 3572 | |||
| 3573 | for (let i = 0; i < KNOWN_DECORATORS.length; i++) { | ||
| 3574 | if (data.startsWith(KNOWN_DECORATORS[i])) { | ||
| 3575 | return true; | ||
| 3576 | } | ||
| 3577 | } | ||
| 3578 | return false; | ||
| 3579 | }; | ||
| 3580 | |||
| 3581 | if (content.startsWith('@@')) { | ||
| 3582 | let newContent = content; | ||
| 3583 | const splited = content.split('\n'); | ||
| 3584 | let decorators = []; | ||
| 3585 | let fallbacked = false; | ||
| 3586 | |||
| 3587 | for (let i = 0; i < splited.length; i++) { | ||
| 3588 | if (splited[i].startsWith('@@')) { | ||
| 3589 | if (splited[i].startsWith('@@@') && !fallbacked) { | ||
| 3590 | continue; | ||
| 3591 | } | ||
| 3592 | |||
| 3593 | if (isKnownDecorator(splited[i])) { | ||
| 3594 | decorators.push(splited[i].startsWith('@@@') ? splited[i].substring(1) : splited[i]); | ||
| 3595 | fallbacked = false; | ||
| 3596 | } | ||
| 3597 | else { | ||
| 3598 | fallbacked = true; | ||
| 3599 | } | ||
| 3600 | } else { | ||
| 3601 | newContent = splited.slice(i).join('\n'); | ||
| 3602 | break; | ||
| 3603 | } | ||
| 3604 | } | ||
| 3605 | return [decorators, newContent]; | ||
| 3606 | } | ||
| 3607 | |||
| 3608 | return [[], content]; | ||
| 3609 | |||
| 3610 | } | ||
| 3611 | |||
| 3548 | /** | 3612 | /** |
| 3549 | * Performs a scan on the chat and returns the world info activated. | 3613 | * Performs a scan on the chat and returns the world info activated. |
| 3550 | * @param {string[]} chat The chat messages to scan, in reverse order. | 3614 | * @param {string[]} chat The chat messages to scan, in reverse order. |
| @@ -3686,6 +3750,17 @@ async function checkWorldInfo(chat, maxContext, isDryRun) { | |||
| 3686 | continue; | 3750 | continue; |
| 3687 | } | 3751 | } |
| 3688 | 3752 | ||
| 3753 | if (entry.decorators.includes('@@activate')) { | ||
| 3754 | log('activated by @@activate decorator'); | ||
| 3755 | activatedNow.add(entry); | ||
| 3756 | continue; | ||
| 3757 | } | ||
| 3758 | |||
| 3759 | if (entry.decorators.includes('@@dont_activate')) { | ||
| 3760 | log('suppressed by @@dont_activate decorator'); | ||
| 3761 | continue; | ||
| 3762 | } | ||
| 3763 | |||
| 3689 | // Now do checks for immediate activations | 3764 | // Now do checks for immediate activations |
| 3690 | if (entry.constant) { | 3765 | if (entry.constant) { |
| 3691 | log('activated because of constant'); | 3766 | log('activated because of constant'); |
| @@ -23,9 +23,21 @@ const write = (image, data) => { | |||
| 23 | } | 23 | } |
| 24 | } | 24 | } |
| 25 | 25 | ||
| 26 | // Add new chunks before the IEND chunk | 26 | // Add new v2 chunk before the IEND chunk |
| 27 | const base64EncodedData = Buffer.from(data, 'utf8').toString('base64'); | 27 | const base64EncodedData = Buffer.from(data, 'utf8').toString('base64'); |
| 28 | chunks.splice(-1, 0, PNGtext.encode('chara', base64EncodedData)); | 28 | chunks.splice(-1, 0, PNGtext.encode('chara', base64EncodedData)); |
| 29 | |||
| 30 | // Try adding v3 chunk before the IEND chunk | ||
| 31 | try { | ||
| 32 | //change v2 format to v3 | ||
| 33 | const v3Data = JSON.parse(data); | ||
| 34 | v3Data.spec = 'chara_card_v3'; | ||
| 35 | v3Data.spec_version = '3.0'; | ||
| 36 | |||
| 37 | const base64EncodedData = Buffer.from(JSON.stringify(v3Data), 'utf8').toString('base64'); | ||
| 38 | chunks.splice(-1, 0, PNGtext.encode('ccv3', base64EncodedData)); | ||
| 39 | } catch (error) { } | ||
| 40 | |||
| 29 | const newBuffer = Buffer.from(encode(chunks)); | 41 | const newBuffer = Buffer.from(encode(chunks)); |
| 30 | return newBuffer; | 42 | return newBuffer; |
| 31 | }; | 43 | }; |
| @@ -408,6 +408,9 @@ function charaFormatData(data, directories) { | |||
| 408 | //_.set(char, 'data.extensions.avatar', 'none'); | 408 | //_.set(char, 'data.extensions.avatar', 'none'); |
| 409 | //_.set(char, 'data.extensions.chat', data.ch_name + ' - ' + humanizedISO8601DateTime()); | 409 | //_.set(char, 'data.extensions.chat', data.ch_name + ' - ' + humanizedISO8601DateTime()); |
| 410 | 410 | ||
| 411 | // V3 fields | ||
| 412 | _.set(char, 'data.group_only_greetings', data.group_only_greetings ?? []); | ||
| 413 | |||
| 411 | if (data.world) { | 414 | if (data.world) { |
| 412 | try { | 415 | try { |
| 413 | const file = readWorldInfoFile(directories, data.world, false); | 416 | const file = readWorldInfoFile(directories, data.world, false); |