修复BGM规范化契约与基础校验
补全T0一键简化两轮输入、结构化结果和互斥状态规则 新增TypeScript Unicode White_Space canonicalizer与生成、补全、简化状态 新增Rust零分配canonicalizer并让计数和校验返回规范化Prompt 补齐跨语言Unicode及200/201字符边界测试
This commit is contained in:
@@ -6,25 +6,86 @@ import {
|
||||
BACKGROUND_MUSIC_PROMPT_MAX_CODE_POINTS,
|
||||
canCompleteBackgroundMusicPrompt,
|
||||
canGenerateBackgroundMusicFromPrompt,
|
||||
canSimplifyBackgroundMusicPrompt,
|
||||
canonicalizeBackgroundMusicPrompt,
|
||||
countEffectivePromptCodePoints,
|
||||
countPromptCodePoints,
|
||||
} from './ImageCanvasBackgroundMusicPromptModel';
|
||||
|
||||
describe('ImageCanvasBackgroundMusicPromptModel', () => {
|
||||
it('counts raw Unicode code points without normalizing the prompt', () => {
|
||||
const combiningText = 'e\u0301';
|
||||
const zwjEmoji = '👨👩👧👦';
|
||||
const prompt = ` \r\n${combiningText}${zwjEmoji}\u200B\uFEFF `;
|
||||
it('removes only leading and trailing Unicode White_Space code points', () => {
|
||||
const unicodeWhiteSpace = [
|
||||
'\u0009',
|
||||
'\u000A',
|
||||
'\u000B',
|
||||
'\u000C',
|
||||
'\u000D',
|
||||
'\u0020',
|
||||
'\u0085',
|
||||
'\u00A0',
|
||||
'\u1680',
|
||||
'\u2000',
|
||||
'\u2001',
|
||||
'\u2002',
|
||||
'\u2003',
|
||||
'\u2004',
|
||||
'\u2005',
|
||||
'\u2006',
|
||||
'\u2007',
|
||||
'\u2008',
|
||||
'\u2009',
|
||||
'\u200A',
|
||||
'\u2028',
|
||||
'\u2029',
|
||||
'\u202F',
|
||||
'\u205F',
|
||||
'\u3000',
|
||||
].join('');
|
||||
const internalWhiteSpace = 'A \t\r\n\u0085\u00A0\u3000B';
|
||||
|
||||
expect(countPromptCodePoints('')).toBe(0);
|
||||
expect(countPromptCodePoints('\r\n')).toBe(2);
|
||||
expect(countPromptCodePoints(combiningText)).toBe(2);
|
||||
expect(countPromptCodePoints(zwjEmoji)).toBe(7);
|
||||
expect(countPromptCodePoints(prompt)).toBe(15);
|
||||
expect(prompt).toBe(` \r\n${combiningText}${zwjEmoji}\u200B\uFEFF `);
|
||||
expect(
|
||||
canonicalizeBackgroundMusicPrompt(
|
||||
`${unicodeWhiteSpace}${internalWhiteSpace}${unicodeWhiteSpace}`,
|
||||
),
|
||||
).toBe(internalWhiteSpace);
|
||||
expect(
|
||||
canonicalizeBackgroundMusicPrompt('\u0085\u200B内容\uFEFF\u0085'),
|
||||
).toBe('\u200B内容\uFEFF');
|
||||
expect(canonicalizeBackgroundMusicPrompt('\u200B\uFEFF')).toBe(
|
||||
'\u200B\uFEFF',
|
||||
);
|
||||
});
|
||||
|
||||
it('counts only non-Unicode-White_Space code points as effective', () => {
|
||||
it('is idempotent without normalizing internal Unicode content', () => {
|
||||
const combiningText = 'e\u0301';
|
||||
const zwjEmoji = '👨👩👧👦';
|
||||
const prompt = ` \u0085${combiningText}\r\n${zwjEmoji}\u200B\uFEFF\u3000`;
|
||||
const canonicalPrompt = `${combiningText}\r\n${zwjEmoji}\u200B\uFEFF`;
|
||||
|
||||
expect(canonicalizeBackgroundMusicPrompt(prompt)).toBe(canonicalPrompt);
|
||||
expect(canonicalizeBackgroundMusicPrompt(canonicalPrompt)).toBe(
|
||||
canonicalPrompt,
|
||||
);
|
||||
expect(canonicalPrompt).toContain(combiningText);
|
||||
expect(canonicalPrompt).toContain('\r\n');
|
||||
expect(canonicalPrompt).toContain(zwjEmoji);
|
||||
expect(canonicalPrompt).toContain('\u200B\uFEFF');
|
||||
});
|
||||
|
||||
it('counts code points from the canonical prompt', () => {
|
||||
const combiningText = 'e\u0301';
|
||||
const zwjEmoji = '👨👩👧👦';
|
||||
const prompt = ` \r\n${combiningText}\r\n${zwjEmoji}\u200B\uFEFF `;
|
||||
|
||||
expect(countPromptCodePoints('')).toBe(0);
|
||||
expect(countPromptCodePoints('\r\n')).toBe(0);
|
||||
expect(countPromptCodePoints(combiningText)).toBe(2);
|
||||
expect(countPromptCodePoints(zwjEmoji)).toBe(7);
|
||||
expect(countPromptCodePoints(prompt)).toBe(13);
|
||||
expect(prompt).toBe(` \r\n${combiningText}\r\n${zwjEmoji}\u200B\uFEFF `);
|
||||
});
|
||||
|
||||
it('counts only non-Unicode-White_Space code points in the canonical prompt as effective', () => {
|
||||
expect(countEffectivePromptCodePoints(' \t\r\n\u0085\u00A0\u3000')).toBe(0);
|
||||
expect(countEffectivePromptCodePoints('e\u0301')).toBe(2);
|
||||
expect(countEffectivePromptCodePoints('👨👩👧👦')).toBe(7);
|
||||
@@ -57,4 +118,91 @@ describe('ImageCanvasBackgroundMusicPromptModel', () => {
|
||||
expect(canCompleteBackgroundMusicPrompt('😀'.repeat(200))).toBe(true);
|
||||
expect(canCompleteBackgroundMusicPrompt('😀'.repeat(201))).toBe(false);
|
||||
});
|
||||
|
||||
it('allows simplification only for a non-empty canonical prompt over 200 code points', () => {
|
||||
expect(canSimplifyBackgroundMusicPrompt(' \r\n\u0085')).toBe(false);
|
||||
expect(canSimplifyBackgroundMusicPrompt('A'.repeat(200))).toBe(false);
|
||||
expect(canSimplifyBackgroundMusicPrompt('A'.repeat(201))).toBe(true);
|
||||
});
|
||||
|
||||
it.each([200, 201])(
|
||||
'canonicalizes %i Unicode White_Space code points to an empty disabled prompt',
|
||||
(length) => {
|
||||
const prompt = '\u0085'.repeat(length);
|
||||
|
||||
expect(canonicalizeBackgroundMusicPrompt(prompt)).toBe('');
|
||||
expect(countPromptCodePoints(prompt)).toBe(0);
|
||||
expect(countEffectivePromptCodePoints(prompt)).toBe(0);
|
||||
expect(canGenerateBackgroundMusicFromPrompt(prompt)).toBe(false);
|
||||
expect(canCompleteBackgroundMusicPrompt(prompt)).toBe(false);
|
||||
expect(canSimplifyBackgroundMusicPrompt(prompt)).toBe(false);
|
||||
},
|
||||
);
|
||||
|
||||
it('uses the canonical prompt for generation, completion, and simplification states', () => {
|
||||
const boundaryWhiteSpaceAroundA = `${'\u0085'.repeat(201)}A${'\u3000'.repeat(201)}`;
|
||||
const overLimitWithInternalWhiteSpace = `A${' '.repeat(199)}B`;
|
||||
const boundaryWhiteSpaceAround200Characters = `${'\u0085'.repeat(201)}${'A'.repeat(200)}${'\u3000'.repeat(201)}`;
|
||||
|
||||
expect(canonicalizeBackgroundMusicPrompt(boundaryWhiteSpaceAroundA)).toBe(
|
||||
'A',
|
||||
);
|
||||
expect(countPromptCodePoints(boundaryWhiteSpaceAroundA)).toBe(1);
|
||||
expect(countEffectivePromptCodePoints(boundaryWhiteSpaceAroundA)).toBe(1);
|
||||
expect(
|
||||
canGenerateBackgroundMusicFromPrompt(boundaryWhiteSpaceAroundA),
|
||||
).toBe(true);
|
||||
expect(canCompleteBackgroundMusicPrompt(boundaryWhiteSpaceAroundA)).toBe(
|
||||
false,
|
||||
);
|
||||
expect(canSimplifyBackgroundMusicPrompt(boundaryWhiteSpaceAroundA)).toBe(
|
||||
false,
|
||||
);
|
||||
|
||||
expect(countPromptCodePoints(overLimitWithInternalWhiteSpace)).toBe(201);
|
||||
expect(
|
||||
countEffectivePromptCodePoints(overLimitWithInternalWhiteSpace),
|
||||
).toBe(2);
|
||||
expect(
|
||||
canGenerateBackgroundMusicFromPrompt(overLimitWithInternalWhiteSpace),
|
||||
).toBe(false);
|
||||
expect(
|
||||
canCompleteBackgroundMusicPrompt(overLimitWithInternalWhiteSpace),
|
||||
).toBe(false);
|
||||
expect(
|
||||
canSimplifyBackgroundMusicPrompt(overLimitWithInternalWhiteSpace),
|
||||
).toBe(true);
|
||||
|
||||
expect(countPromptCodePoints(boundaryWhiteSpaceAround200Characters)).toBe(
|
||||
200,
|
||||
);
|
||||
expect(
|
||||
countEffectivePromptCodePoints(boundaryWhiteSpaceAround200Characters),
|
||||
).toBe(200);
|
||||
expect(
|
||||
canGenerateBackgroundMusicFromPrompt(
|
||||
boundaryWhiteSpaceAround200Characters,
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
canCompleteBackgroundMusicPrompt(boundaryWhiteSpaceAround200Characters),
|
||||
).toBe(true);
|
||||
expect(
|
||||
canSimplifyBackgroundMusicPrompt(boundaryWhiteSpaceAround200Characters),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it.each(['\u200B', '\uFEFF'])(
|
||||
'keeps 201 %s code points and enables only simplification',
|
||||
(codePoint) => {
|
||||
const prompt = codePoint.repeat(201);
|
||||
|
||||
expect(canonicalizeBackgroundMusicPrompt(prompt)).toBe(prompt);
|
||||
expect(countPromptCodePoints(prompt)).toBe(201);
|
||||
expect(countEffectivePromptCodePoints(prompt)).toBe(201);
|
||||
expect(canGenerateBackgroundMusicFromPrompt(prompt)).toBe(false);
|
||||
expect(canCompleteBackgroundMusicPrompt(prompt)).toBe(false);
|
||||
expect(canSimplifyBackgroundMusicPrompt(prompt)).toBe(true);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -3,14 +3,22 @@ export const BACKGROUND_MUSIC_PROMPT_GENERATION_MIN_EFFECTIVE_CODE_POINTS = 1;
|
||||
export const BACKGROUND_MUSIC_PROMPT_COMPLETION_MIN_EFFECTIVE_CODE_POINTS = 2;
|
||||
|
||||
const UNICODE_WHITE_SPACE_CODE_POINT = /^\p{White_Space}$/u;
|
||||
const LEADING_UNICODE_WHITE_SPACE = /^\p{White_Space}+/u;
|
||||
const TRAILING_UNICODE_WHITE_SPACE = /\p{White_Space}+$/u;
|
||||
|
||||
export function canonicalizeBackgroundMusicPrompt(prompt: string) {
|
||||
return prompt
|
||||
.replace(LEADING_UNICODE_WHITE_SPACE, '')
|
||||
.replace(TRAILING_UNICODE_WHITE_SPACE, '');
|
||||
}
|
||||
|
||||
export function countPromptCodePoints(prompt: string) {
|
||||
return Array.from(prompt).length;
|
||||
return Array.from(canonicalizeBackgroundMusicPrompt(prompt)).length;
|
||||
}
|
||||
|
||||
export function countEffectivePromptCodePoints(prompt: string) {
|
||||
let effectiveCodePoints = 0;
|
||||
for (const codePoint of prompt) {
|
||||
for (const codePoint of canonicalizeBackgroundMusicPrompt(prompt)) {
|
||||
if (!UNICODE_WHITE_SPACE_CODE_POINT.test(codePoint)) {
|
||||
effectiveCodePoints += 1;
|
||||
}
|
||||
@@ -33,3 +41,11 @@ export function canCompleteBackgroundMusicPrompt(prompt: string) {
|
||||
BACKGROUND_MUSIC_PROMPT_COMPLETION_MIN_EFFECTIVE_CODE_POINTS
|
||||
);
|
||||
}
|
||||
|
||||
export function canSimplifyBackgroundMusicPrompt(prompt: string) {
|
||||
return (
|
||||
countPromptCodePoints(prompt) > BACKGROUND_MUSIC_PROMPT_MAX_CODE_POINTS &&
|
||||
countEffectivePromptCodePoints(prompt) >=
|
||||
BACKGROUND_MUSIC_PROMPT_GENERATION_MIN_EFFECTIVE_CODE_POINTS
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user