Merge pull request #2512 from kwaroran/ccv3-write CCv3 Partial Implementation

623cab0fa578e2d1a45a6d01b9ba9417d6ec3333

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
5 files changed, +93 -1Showing whitespace changes
public/scripts/macros.js+1 -0
@@ -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(''));
467468
468 content = content.replace(/\{\{\/\/([\s\S]*?)\}\}/gm, '');469 content = content.replace(/\{\{\/\/([\s\S]*?)\}\}/gm, '');
469470
public/scripts/templates/macros.html+1 -0
@@ -28,6 +28,7 @@
28 <li><tt>&lcub;&lcub;firstIncludedMessageId&rcub;&rcub;</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>&lcub;&lcub;firstIncludedMessageId&rcub;&rcub;</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>&lcub;&lcub;currentSwipeId&rcub;&rcub;</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>&lcub;&lcub;currentSwipeId&rcub;&rcub;</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>&lcub;&lcub;lastSwipeId&rcub;&rcub;</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>&lcub;&lcub;lastSwipeId&rcub;&rcub;</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>&lcub;&lcub;reverse:(content)&rcub;&rcub;</tt> – <span data-i18n="help_macros_reverse">reverses the content of the macro.</span></li>
31 <li><tt>&lcub;&lcub;// (note)&rcub;&rcub;</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>&lcub;&lcub;// (note)&rcub;&rcub;</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>&lcub;&lcub;time&rcub;&rcub;</tt> – <span data-i18n="help_macros_26">the current time</span></li>33 <li><tt>&lcub;&lcub;time&rcub;&rcub;</tt> – <span data-i18n="help_macros_26">the current time</span></li>
33 <li><tt>&lcub;&lcub;date&rcub;&rcub;</tt> – <span data-i18n="help_macros_27">the current date</span></li>34 <li><tt>&lcub;&lcub;date&rcub;&rcub;</tt> – <span data-i18n="help_macros_27">the current date</span></li>
public/scripts/world-info.js+75 -0
@@ -107,6 +107,7 @@ const METADATA_KEY = 'world_info';
107const DEFAULT_DEPTH = 4;107const DEFAULT_DEPTH = 4;
108const DEFAULT_WEIGHT = 100;108const DEFAULT_WEIGHT = 100;
109const MAX_SCAN_DEPTH = 1000;109const MAX_SCAN_DEPTH = 1000;
110const KNOWN_DECORATORS = ['@@activate', '@@dont_activate'];
110111
111// Typedef area112// Typedef area
112/**113/**
@@ -123,6 +124,7 @@ const MAX_SCAN_DEPTH = 1000;
123 * @property {number} [sticky] The sticky value of the entry124 * @property {number} [sticky] The sticky value of the entry
124 * @property {number} [cooldown] The cooldown of the entry125 * @property {number} [cooldown] The cooldown of the entry
125 * @property {number} [delay] The delay of the entry126 * @property {number} [delay] The delay of the entry
127 * @property {string[]} [decorators] Array of decorators for the entry
126 */128 */
127129
128/**130/**
@@ -3534,6 +3536,12 @@ export async function getSortedEntries() {
3534 // Chat lore always goes first3536 // Chat lore always goes first
3535 entries = [...chatLore.sort(sortFn), ...entries];3537 entries = [...chatLore.sort(sortFn), ...entries];
35363538
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));
35383546
3539 // Need to deep clone the entries to avoid modifying the cached data3547 // Need to deep clone the entries to avoid modifying the cached data
@@ -3545,6 +3553,62 @@ export async function getSortedEntries() {
3545 }3553 }
3546}3554}
35473555
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*/
3562function 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 }
36883752
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 activations3764 // 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');
src/character-card-parser.js+13 -1
@@ -23,9 +23,21 @@ const write = (image, data) => {
23 }23 }
24 }24 }
2525
26 // Add new chunks before the IEND chunk26 // 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};
src/endpoints/characters.js+3 -0
@@ -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());
410410
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);