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

623cab0fa578e2d1a45a6d01b9ba9417d6ec3333

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

Signed
5 files changed, +93 -1Ignore whitespace
public/scripts/macros.js+1 -0
@@ -464,6 +464,7 @@ export function evaluateMacros(content, env) {
464464 content = content.replace(/{{firstIncludedMessageId}}/gi, () => String(getFirstIncludedMessageId() ?? ''));
465465 content = content.replace(/{{lastSwipeId}}/gi, () => String(getLastSwipeId() ?? ''));
466466 content = content.replace(/{{currentSwipeId}}/gi, () => String(getCurrentSwipeId() ?? ''));
467+ content = content.replace(/{{reverse\:(.+?)}}/gi, (_, str) => Array.from(str).reverse().join(''));
467468
468469 content = content.replace(/\{\{\/\/([\s\S]*?)\}\}/gm, '');
469470
public/scripts/templates/macros.html+1 -0
@@ -28,6 +28,7 @@
2828 <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>
2929 <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>
3030 <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>
3132 <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>
3233 <li><tt>&lcub;&lcub;time&rcub;&rcub;</tt> – <span data-i18n="help_macros_26">the current time</span></li>
3334 <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';
107107const DEFAULT_DEPTH = 4;
108108const DEFAULT_WEIGHT = 100;
109109const MAX_SCAN_DEPTH = 1000;
110+const KNOWN_DECORATORS = ['@@activate', '@@dont_activate'];
110111
111112// Typedef area
112113/**
@@ -123,6 +124,7 @@ const MAX_SCAN_DEPTH = 1000;
123124 * @property {number} [sticky] The sticky value of the entry
124125 * @property {number} [cooldown] The cooldown of the entry
125126 * @property {number} [delay] The delay of the entry
127+ * @property {string[]} [decorators] Array of decorators for the entry
126128 */
127129
128130/**
@@ -3534,6 +3536,12 @@ export async function getSortedEntries() {
35343536 // Chat lore always goes first
35353537 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+
35373545 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
35393547 // Need to deep clone the entries to avoid modifying the cached data
@@ -3545,6 +3553,62 @@ export async function getSortedEntries() {
35453553 }
35463554}
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+*/
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+
35483612/**
35493613 * Performs a scan on the chat and returns the world info activated.
35503614 * @param {string[]} chat The chat messages to scan, in reverse order.
@@ -3686,6 +3750,17 @@ async function checkWorldInfo(chat, maxContext, isDryRun) {
36863750 continue;
36873751 }
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+
36893764 // Now do checks for immediate activations
36903765 if (entry.constant) {
36913766 log('activated because of constant');
src/character-card-parser.js+13 -1
@@ -23,9 +23,21 @@ const write = (image, data) => {
2323 }
2424 }
2525
2626 // Add new chunksv2 chunk before the IEND chunk
2727 const base64EncodedData = Buffer.from(data, 'utf8').toString('base64');
2828 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+
2941 const newBuffer = Buffer.from(encode(chunks));
3042 return newBuffer;
3143};
src/endpoints/characters.js+3 -0
@@ -408,6 +408,9 @@ function charaFormatData(data, directories) {
408408 //_.set(char, 'data.extensions.avatar', 'none');
409409 //_.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+
411414 if (data.world) {
412415 try {
413416 const file = readWorldInfoFile(directories, data.world, false);