mirror of
https://github.com/moeru-ai/airi.git
synced 2026-10-30 19:34:10 +08:00
fix(stage-ui): preserve macro-looking character and user names
This commit is contained in:
@@ -482,3 +482,15 @@ it('reuses one real Worker for all regex checks in a compilation', async () => {
|
||||
vi.unstubAllGlobals()
|
||||
}
|
||||
})
|
||||
|
||||
it('preserves macro-looking names without interpreting inserted text again', async () => {
|
||||
const card = createCard({
|
||||
name: '{{user}} {{reverse:abc}}',
|
||||
greetings: ['{{char}} / {{user}} / <bot> / <user> / {{reverse:abc}}'],
|
||||
systemPrompt: '{{char}} / {{user}} / {{reverse:abc}}',
|
||||
})
|
||||
const userName = '{{roll:d6}} {{comment:keep}}'
|
||||
expect(compileCharacterCardGreeting(card, { userName })).toBe(`${card.name} / ${userName} / ${card.name} / ${userName} / cba`)
|
||||
const result = await compileCharacterCardMessages(card, [{ role: 'system', content: compileCharacterCardSystemPrompt(card) }], { userName })
|
||||
expect(result[0]?.content).toContain(`${card.name} / ${userName} / cba`)
|
||||
})
|
||||
|
||||
@@ -506,26 +506,32 @@ function expandCharacterCardMacros(
|
||||
const userName = options.userName?.trim() || 'User'
|
||||
const random = options.random ?? Math.random
|
||||
|
||||
return source
|
||||
.replace(/\{\{char\}\}|<char>|<bot>/gi, () => characterName)
|
||||
.replace(/\{\{user\}\}|<user>/gi, () => userName)
|
||||
.replace(/\{\{\/\/[^}]*\}\}/g, '')
|
||||
.replace(/\{\{(?:hidden_key|comment):[^}]*\}\}/gi, '')
|
||||
.replace(/\{\{reverse:([^}]*)\}\}/gi, (_, value: string) => [...value].reverse().join(''))
|
||||
.replace(/\{\{roll:d?(\d+)\}\}/gi, (_, rawSides: string) => {
|
||||
const sides = Number.parseInt(rawSides, 10)
|
||||
if (sides < 1)
|
||||
return ''
|
||||
return String(Math.floor(random() * sides) + 1)
|
||||
})
|
||||
.replace(/\{\{random:([^}]*)\}\}/gi, (_, rawValues: string) => {
|
||||
const values = splitEscapedCommaList(rawValues)
|
||||
return values[Math.floor(random() * values.length)] ?? ''
|
||||
})
|
||||
.replace(/\{\{pick:([^}]*)\}\}/gi, (_, rawValues: string) => {
|
||||
const values = splitEscapedCommaList(rawValues)
|
||||
return values[stableStringHash(rawValues) % values.length] ?? ''
|
||||
})
|
||||
// Replace source tokens once; inserted names are literal, even if they look like macros.
|
||||
return source.replace(/\{\{(char|user|\/\/[^}]*|(?:hidden_key|comment|reverse|random|pick):[^}]*|roll:d?\d+)\}\}|<(char|bot|user)>/gi, (_match, macro: string | undefined, legacy: string | undefined) => {
|
||||
const token = macro ?? legacy!
|
||||
const name = token.toLowerCase()
|
||||
if (name === 'char' || name === 'bot')
|
||||
return characterName
|
||||
if (name === 'user')
|
||||
return userName
|
||||
if (token.startsWith('//'))
|
||||
return ''
|
||||
|
||||
const separator = token.indexOf(':')
|
||||
const kind = token.slice(0, separator).toLowerCase()
|
||||
const value = token.slice(separator + 1)
|
||||
if (kind === 'hidden_key' || kind === 'comment')
|
||||
return ''
|
||||
if (kind === 'reverse')
|
||||
return [...value].reverse().join('')
|
||||
if (kind === 'roll') {
|
||||
const sides = Number.parseInt(value.replace(/^d/i, ''), 10)
|
||||
return sides < 1 ? '' : String(Math.floor(random() * sides) + 1)
|
||||
}
|
||||
const values = splitEscapedCommaList(value)
|
||||
const index = kind === 'pick' ? stableStringHash(value) % values.length : Math.floor(random() * values.length)
|
||||
return values[index] ?? ''
|
||||
})
|
||||
}
|
||||
|
||||
function splitEscapedCommaList(source: string): string[] {
|
||||
|
||||
Reference in New Issue
Block a user