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 -1Ignore whitespace
public/script.js+15 -0
@@ -3230,6 +3230,8 @@ export function baseChatReplace(value, name1Override = null, name2Override = nul
32303230 * @property {string} version Character version
32313231 * @property {string} charDepthPrompt Character depth note
32323232 * @property {string} creatorNotes Character creator notes
3233+ * @property {string} firstMessage Character first message / greeting
3234+ * @property {string[]} alternateGreetings Character alternate greetings
32333235 */
32343236
32353237/**
@@ -3316,6 +3318,17 @@ export function getCharacterCardFieldsLazy({ chid = undefined } = {}) {
33163318 const exampleDialog = chat_metadata.mes_example || character.mes_example || '';
33173319 return baseChatReplace(exampleDialog.trim());
33183320 },
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+ },
33193332 };
33203333
33213334 return createLazyFields(resolvers);
@@ -3342,6 +3355,8 @@ export function getCharacterCardFields({ chid = undefined } = {}) {
33423355 version: lazy.version,
33433356 charDepthPrompt: lazy.charDepthPrompt,
33443357 creatorNotes: lazy.creatorNotes,
3358+ firstMessage: lazy.firstMessage,
3359+ alternateGreetings: lazy.alternateGreetings,
33453360 };
33463361}
33473362
public/scripts/macros/definitions/env-macros.js+24 -0
@@ -140,6 +140,30 @@ export function registerEnvMacros() {
140140 handler: ({ env }) => env.character.creatorNotes ?? '',
141141 });
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+
143167 // Character version macros (legacy variants and documented {{charVersion}})
144168 MacroRegistry.registerMacro('charVersion', {
145169 aliases: [
public/scripts/macros/engine/MacroEnv.types.js+2 -0
@@ -38,6 +38,8 @@
3838 * @property {string} [charDepthPrompt]
3939 * @property {string} [creatorNotes]
4040 * @property {string} [version]
41+ * @property {string} [firstMessage]
42+ * @property {string[]} [alternateGreetings]
4143 */
4244
4345/**
public/scripts/macros/engine/MacroEnvBuilder.js+10 -1
@@ -109,10 +109,19 @@ class MacroEnvBuilder {
109109 ['version', 'version'],
110110 ['charDepthPrompt', 'charDepthPrompt'],
111111 ['creatorNotes', 'creatorNotes'],
112+ ['firstMessage', 'firstMessage'],
113+ ['alternateGreetings', 'alternateGreetings'],
112114 ]);
113115 for (const [envKey, fieldKey] of fieldMappings) {
114116 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+ },
116125 enumerable: true,
117126 configurable: true,
118127 });
tests/frontend/MacroEnvBuilder.e2e.js+2 -0
@@ -98,6 +98,8 @@ test.describe('MacroEnvBuilder', () => {
9898 'version',
9999 'charDepthPrompt',
100100 'creatorNotes',
101+ 'firstMessage',
102+ 'alternateGreetings',
101103 ]));
102104 });
103105