281c84b7bf
Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/142 Co-authored-by: Linghong <ink29535@proton.me> Co-committed-by: Linghong <ink29535@proton.me>
244 lines
6.4 KiB
TypeScript
244 lines
6.4 KiB
TypeScript
import { canonicalizeBackgroundMusicPrompt } from './ImageCanvasBackgroundMusicPromptModel';
|
||
|
||
export type BackgroundMusicPromptPresetGroup =
|
||
| 'purpose'
|
||
| 'atmosphere'
|
||
| 'scene';
|
||
|
||
export type BackgroundMusicPromptPreset = {
|
||
id: string;
|
||
group: BackgroundMusicPromptPresetGroup;
|
||
label: string;
|
||
prompt: string;
|
||
};
|
||
|
||
const UNICODE_PUNCTUATION_CODE_POINT = /^\p{Punctuation}$/u;
|
||
const BACKGROUND_MUSIC_PRESET_SENTENCE_SEPARATOR = '。';
|
||
|
||
export const BACKGROUND_MUSIC_PROMPT_PRESET_GROUPS = [
|
||
'purpose',
|
||
'atmosphere',
|
||
'scene',
|
||
] as const satisfies readonly BackgroundMusicPromptPresetGroup[];
|
||
|
||
/**
|
||
* 权威设计《画板音乐生成入口设计》「预设库与追加规则」的固定文案,逐项复制,不改写。
|
||
*
|
||
* `id`、`group` 只用于滚动轨道的颜色分组和 React key,`label` 只用于 chip 展示;
|
||
* 写入输入框的永远只有 `prompt`,隐藏元数据不得进入 Prompt 或 Suno 请求。
|
||
*/
|
||
export const BACKGROUND_MUSIC_PROMPT_PRESETS = [
|
||
{
|
||
id: 'menu-idle',
|
||
group: 'purpose',
|
||
label: '菜单待机',
|
||
prompt: '低干扰、适合菜单待机的背景音乐',
|
||
},
|
||
{
|
||
id: 'casual-match',
|
||
group: 'purpose',
|
||
label: '休闲消除',
|
||
prompt: '轻快可爱的休闲消除背景音乐',
|
||
},
|
||
{
|
||
id: 'puzzle-thinking',
|
||
group: 'purpose',
|
||
label: '解谜思考',
|
||
prompt: '安静专注的解谜思考背景音乐',
|
||
},
|
||
{
|
||
id: 'exploration-adventure',
|
||
group: 'purpose',
|
||
label: '探索冒险',
|
||
prompt: '温和推进的探索冒险背景音乐',
|
||
},
|
||
{
|
||
id: 'dialogue-scene',
|
||
group: 'purpose',
|
||
label: '对白场景',
|
||
prompt: '克制柔和、留出对白空间的背景音乐',
|
||
},
|
||
{
|
||
id: 'pre-battle',
|
||
group: 'purpose',
|
||
label: '战斗前',
|
||
prompt: '蓄势待发的战斗前背景音乐',
|
||
},
|
||
{
|
||
id: 'victory-settlement',
|
||
group: 'purpose',
|
||
label: '胜利结算',
|
||
prompt: '明亮满足的胜利结算背景音乐',
|
||
},
|
||
{
|
||
id: 'defeat-settlement',
|
||
group: 'purpose',
|
||
label: '失败结算',
|
||
prompt: '克制低落的失败结算背景音乐',
|
||
},
|
||
{
|
||
id: 'daily-management',
|
||
group: 'purpose',
|
||
label: '日常经营',
|
||
prompt: '轻松有序的日常经营背景音乐',
|
||
},
|
||
{
|
||
id: 'farm-management',
|
||
group: 'purpose',
|
||
label: '农场经营',
|
||
prompt: '自然温暖的农场经营背景音乐',
|
||
},
|
||
{
|
||
id: 'campus-daily',
|
||
group: 'purpose',
|
||
label: '校园日常',
|
||
prompt: '青春轻松的校园日常背景音乐',
|
||
},
|
||
{
|
||
id: 'food-kitchen',
|
||
group: 'purpose',
|
||
label: '美食厨房',
|
||
prompt: '温暖活泼的美食厨房背景音乐',
|
||
},
|
||
{
|
||
id: 'warm-healing',
|
||
group: 'atmosphere',
|
||
label: '温暖治愈',
|
||
prompt: '柔和明亮的治愈背景音乐',
|
||
},
|
||
{
|
||
id: 'mystery-suspense',
|
||
group: 'atmosphere',
|
||
label: '神秘悬疑',
|
||
prompt: '克制神秘的悬疑背景音乐',
|
||
},
|
||
{
|
||
id: 'tension-buildup',
|
||
group: 'atmosphere',
|
||
label: '紧张推进',
|
||
prompt: '稳定推进、逐渐紧张的背景音乐',
|
||
},
|
||
{
|
||
id: 'forest-nature',
|
||
group: 'scene',
|
||
label: '森林自然',
|
||
prompt: '清新自然的森林背景音乐',
|
||
},
|
||
{
|
||
id: 'rainy-night',
|
||
group: 'scene',
|
||
label: '雨夜静谧',
|
||
prompt: '雨夜静谧、略带神秘感的背景音乐',
|
||
},
|
||
{
|
||
id: 'ocean-drift',
|
||
group: 'scene',
|
||
label: '海洋漂流',
|
||
prompt: '开阔舒缓的海洋漂流背景音乐',
|
||
},
|
||
{
|
||
id: 'mountain-journey',
|
||
group: 'scene',
|
||
label: '山野远行',
|
||
prompt: '自由舒展的山野远行背景音乐',
|
||
},
|
||
{
|
||
id: 'candy-park',
|
||
group: 'scene',
|
||
label: '糖果乐园',
|
||
prompt: '甜美活泼的糖果乐园背景音乐',
|
||
},
|
||
{
|
||
id: 'cozy-cabin',
|
||
group: 'scene',
|
||
label: '温馨小屋',
|
||
prompt: '温暖安静的温馨小屋背景音乐',
|
||
},
|
||
{
|
||
id: 'city-night',
|
||
group: 'scene',
|
||
label: '城市夜晚',
|
||
prompt: '克制迷人的城市夜晚背景音乐',
|
||
},
|
||
{
|
||
id: 'space-scifi',
|
||
group: 'scene',
|
||
label: '太空科幻',
|
||
prompt: '空灵未来感的太空科幻背景音乐',
|
||
},
|
||
{
|
||
id: 'cyber-block',
|
||
group: 'scene',
|
||
label: '赛博街区',
|
||
prompt: '冷静律动的赛博街区背景音乐',
|
||
},
|
||
{
|
||
id: 'ancient-fantasy',
|
||
group: 'scene',
|
||
label: '古风幻想',
|
||
prompt: '空灵雅致的古风幻想背景音乐',
|
||
},
|
||
{
|
||
id: 'fairytale-garden',
|
||
group: 'scene',
|
||
label: '童话花园',
|
||
prompt: '梦幻轻盈的童话花园背景音乐',
|
||
},
|
||
{
|
||
id: 'undersea-ruins',
|
||
group: 'scene',
|
||
label: '海底遗迹',
|
||
prompt: '深邃神秘的海底遗迹背景音乐',
|
||
},
|
||
{
|
||
id: 'desert-ruins',
|
||
group: 'scene',
|
||
label: '沙漠遗迹',
|
||
prompt: '苍茫神秘的沙漠遗迹背景音乐',
|
||
},
|
||
{
|
||
id: 'lava-cave',
|
||
group: 'scene',
|
||
label: '熔岩洞穴',
|
||
prompt: '炽热压迫的熔岩洞穴背景音乐',
|
||
},
|
||
{
|
||
id: 'curious-museum',
|
||
group: 'scene',
|
||
label: '奇妙博物馆',
|
||
prompt: '好奇灵动的奇妙博物馆背景音乐',
|
||
},
|
||
] as const satisfies readonly BackgroundMusicPromptPreset[];
|
||
|
||
function readLastCodePoint(value: string) {
|
||
// 必须按 code point 读取:直接取最后一个 UTF-16 code unit 会在 emoji 等
|
||
// surrogate pair 结尾时拿到半个字符,标点判断随之失真。
|
||
const codePoints = Array.from(value);
|
||
return codePoints[codePoints.length - 1];
|
||
}
|
||
|
||
/**
|
||
* 按权威设计的追加规则返回新的可见 Prompt。
|
||
*
|
||
* 输入先复用共享 canonicalizer(不使用会额外删除 U+FEFF 的原生 `trim()`),因此调用方
|
||
* 传入尚未规范化的文本也不会产出 ` 。预设文本` 这类结果;canonicalization 幂等,
|
||
* 已经写回过的文本再算一次结果相同。
|
||
*
|
||
* 不去重、不截断、不改内部空格与换行;追加后允许超过 200 或 2000 字,文本必须完整保留。
|
||
*/
|
||
export function appendBackgroundMusicPromptPreset(
|
||
currentPrompt: string,
|
||
preset: BackgroundMusicPromptPreset,
|
||
) {
|
||
const canonicalPrompt = canonicalizeBackgroundMusicPrompt(currentPrompt);
|
||
if (!canonicalPrompt) {
|
||
return preset.prompt;
|
||
}
|
||
|
||
const lastCodePoint = readLastCodePoint(canonicalPrompt);
|
||
if (lastCodePoint && UNICODE_PUNCTUATION_CODE_POINT.test(lastCodePoint)) {
|
||
return `${canonicalPrompt}${preset.prompt}`;
|
||
}
|
||
return `${canonicalPrompt}${BACKGROUND_MUSIC_PRESET_SENTENCE_SEPARATOR}${preset.prompt}`;
|
||
}
|