Implement {{charFirstMessage}} / {{greeting}} macro with alternate greeting indexing and substitution (#5220) * Initial plan * Implement {{firstMessage}} / {{greeting}} macro for character's first message Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com> * Simplify firstMessage resolver to use || instead of ?? for consistency Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com> * Remove {{firstMessage}} alias, add 0-based greeting index for alternate greetings Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com> * Add alternateGreetings to env, apply substitution to greetings Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com> * Remove legacy greeting macro, keep only new engine implementation Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com> Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com>

b5a1d227fde1f0ca2b0e7947fa993b4010f5c31a

Copilot <198982749+Copilot@users.noreply.github.com>

Signed
5 files changed, +53 -1Showing whitespace changes
public/script.js+15 -0
@@ -3230,6 +3230,8 @@ export function baseChatReplace(value, name1Override = null, name2Override = nul
3230 * @property {string} version Character version3230 * @property {string} version Character version
3231 * @property {string} charDepthPrompt Character depth note3231 * @property {string} charDepthPrompt Character depth note
3232 * @property {string} creatorNotes Character creator notes3232 * @property {string} creatorNotes Character creator notes
3233 * @property {string} firstMessage Character first message / greeting
3234 * @property {string[]} alternateGreetings Character alternate greetings
3233 */3235 */
32343236
3235/**3237/**
@@ -3316,6 +3318,17 @@ export function getCharacterCardFieldsLazy({ chid = undefined } = {}) {
3316 const exampleDialog = chat_metadata.mes_example || character.mes_example || '';3318 const exampleDialog = chat_metadata.mes_example || character.mes_example || '';
3317 return baseChatReplace(exampleDialog.trim());3319 return baseChatReplace(exampleDialog.trim());
3318 },3320 },
3321 firstMessage: () => {
3322 if (!character) return '';
3323 const firstMes = character.first_mes?.trim() || '';
3324 return baseChatReplace(firstMes);
3325 },
3326 alternateGreetings: () => {
3327 if (!character) return [];
3328 const altGreetings = character.data?.alternate_greetings;
3329 if (!Array.isArray(altGreetings)) return [];
3330 return altGreetings.map(greeting => baseChatReplace(greeting?.trim()));
3331 },
3319 };3332 };
33203333
3321 return createLazyFields(resolvers);3334 return createLazyFields(resolvers);
@@ -3342,6 +3355,8 @@ export function getCharacterCardFields({ chid = undefined } = {}) {
3342 version: lazy.version,3355 version: lazy.version,
3343 charDepthPrompt: lazy.charDepthPrompt,3356 charDepthPrompt: lazy.charDepthPrompt,
3344 creatorNotes: lazy.creatorNotes,3357 creatorNotes: lazy.creatorNotes,
3358 firstMessage: lazy.firstMessage,
3359 alternateGreetings: lazy.alternateGreetings,
3345 };3360 };
3346}3361}
33473362
public/scripts/macros/definitions/env-macros.js+24 -0
@@ -140,6 +140,30 @@ export function registerEnvMacros() {
140 handler: ({ env }) => env.character.creatorNotes ?? '',140 handler: ({ env }) => env.character.creatorNotes ?? '',
141 });141 });
142142
143 MacroRegistry.registerMacro('charFirstMessage', {
144 aliases: [{ alias: 'greeting' }],
145 category: MacroCategory.CHARACTER,
146 unnamedArgs: [
147 {
148 name: 'index',
149 optional: true,
150 defaultValue: '0',
151 type: MacroValueType.INTEGER,
152 description: '0-based index. 0 (default) returns the main greeting, 1 and up return alternate greetings.',
153 },
154 ],
155 description: 'The character\'s first message / greeting. Optionally specify an index to access alternate greetings.',
156 returns: 'Character greeting at the given index, or empty string if out of bounds.',
157 exampleUsage: ['{{greeting}}', '{{greeting::0}}', '{{greeting::1}}'],
158 handler: ({ env, unnamedArgs: [index] }) => {
159 const i = Number(index ?? 0);
160 if (i === 0) return env.character.firstMessage ?? '';
161 const altGreetings = env.character.alternateGreetings;
162 if (!Array.isArray(altGreetings)) return '';
163 return altGreetings[i - 1] ?? '';
164 },
165 });
166
143 // Character version macros (legacy variants and documented {{charVersion}})167 // Character version macros (legacy variants and documented {{charVersion}})
144 MacroRegistry.registerMacro('charVersion', {168 MacroRegistry.registerMacro('charVersion', {
145 aliases: [169 aliases: [
public/scripts/macros/engine/MacroEnv.types.js+2 -0
@@ -38,6 +38,8 @@
38 * @property {string} [charDepthPrompt]38 * @property {string} [charDepthPrompt]
39 * @property {string} [creatorNotes]39 * @property {string} [creatorNotes]
40 * @property {string} [version]40 * @property {string} [version]
41 * @property {string} [firstMessage]
42 * @property {string[]} [alternateGreetings]
41 */43 */
4244
43/**45/**
public/scripts/macros/engine/MacroEnvBuilder.js+10 -1
@@ -109,10 +109,19 @@ class MacroEnvBuilder {
109 ['version', 'version'],109 ['version', 'version'],
110 ['charDepthPrompt', 'charDepthPrompt'],110 ['charDepthPrompt', 'charDepthPrompt'],
111 ['creatorNotes', 'creatorNotes'],111 ['creatorNotes', 'creatorNotes'],
112 ['firstMessage', 'firstMessage'],
113 ['alternateGreetings', 'alternateGreetings'],
112 ]);114 ]);
113 for (const [envKey, fieldKey] of fieldMappings) {115 for (const [envKey, fieldKey] of fieldMappings) {
114 Object.defineProperty(env.character, envKey, {116 Object.defineProperty(env.character, envKey, {
115 get() { return fields[fieldKey] || ''; },117 get() {
118 const value = fields[fieldKey];
119 // alternateGreetings should default to [] instead of ''
120 if (envKey === 'alternateGreetings') {
121 return Array.isArray(value) ? value : [];
122 }
123 return value || '';
124 },
116 enumerable: true,125 enumerable: true,
117 configurable: true,126 configurable: true,
118 });127 });
tests/frontend/MacroEnvBuilder.e2e.js+2 -0
@@ -98,6 +98,8 @@ test.describe('MacroEnvBuilder', () => {
98 'version',98 'version',
99 'charDepthPrompt',99 'charDepthPrompt',
100 'creatorNotes',100 'creatorNotes',
101 'firstMessage',
102 'alternateGreetings',
101 ]));103 ]));
102 });104 });
103105