Files
Genarrative/server-node/src/prompts/characterAssetPrompts.ts
高物 75944b1f1f
Some checks failed
CI / verify (push) Has been cancelled
1
2026-04-20 21:06:48 +08:00

280 lines
9.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
buildMasterPrompt,
buildVideoActionPrompt,
getActionTemplateById,
} from '../../../packages/shared/src/prompts/qwenSprite.js';
/**
* 角色资产正式 prompt 主源。
*
* 这份脚本当前只承担“正式模型 prompt 层”职责:
* - buildNpcVisualPrompt
* - buildNpcAnimationPrompt
* - buildArkCharacterAnimationPrompt
* - buildImageSequencePrompt
*
* 当前仓库状态需要特别区分:
* - 当前自定义世界角色资产工坊默认输入框,实际直接使用前端
* src/prompts/customWorldRolePromptDefaults.ts
* - 默认描述文本的唯一主源已经统一为前端本地映射,
* 不再保留后端独立 bundle 编译接口
* - 当前正式角色主图与动作生成,仍然走本文件里的正式 prompt builder
*/
function clampPromptSeedText(value: unknown, maxLength: number) {
if (typeof value !== 'string') {
return '';
}
return value.replace(/\s+/gu, ' ').trim().slice(0, maxLength);
}
function sanitizeAnimationPromptText(value: string, maxLength: number) {
return value
.replace(/\s+/gu, ' ')
.replace(/||||||||/gu, '')
.replace(/||/gu, '')
.replace(/|/gu, '')
.replace(/|/gu, '')
.trim()
.slice(0, maxLength);
}
function buildCompactAnimationCharacterBrief(value: string) {
const normalized = sanitizeAnimationPromptText(value, 160);
if (!normalized) {
return '';
}
return normalized
.split(/[/|\n,;]+/u)
.map((item) => item.trim())
.filter(Boolean)
.slice(0, 4)
.join('');
}
/**
* 正式角色主图 prompt 编译入口。
*
* 输入的 promptText 通常是资产工坊输入框里的“形象描述”文本;
* 这里会把它和角色摘要合并,再交给共享层 buildMasterPrompt
* 产出真正发给图像模型的正式 prompt。
*
* 因此:
* - promptText = 默认描述文本层
* - buildNpcVisualPrompt 返回值 = 正式图像生成 prompt 层
*/
export function buildNpcVisualPrompt(promptText: string, characterBriefText = '') {
const mergedBrief = [characterBriefText.trim(), promptText.trim()]
.filter(Boolean)
.join('\n');
return buildMasterPrompt(
mergedBrief || '自定义世界角色,服装完整,姿态自然。',
);
}
/**
* 正式角色主图生成的负向提示词。
*
* 只服务于图像生成请求,不参与默认描述文本生成。
*/
export function buildNpcVisualNegativePrompt() {
return [
'正面视角',
'左朝向',
'完全 90 度纯右视图',
'镜头透视',
'半身像',
'脚被裁切',
'头顶被裁切',
'多角色',
'复杂背景',
'建筑场景',
'漂浮物',
'烟雾环境',
'武器消失',
'武器换手',
'额外手臂',
'额外腿',
'服装变化',
'脸部变化',
'模糊',
'运动模糊',
'文字',
'水印',
'UI 元素',
'软萌 Q版大头贴',
'儿童绘本风',
'厚涂插画感',
'低对比柔边',
].join('');
}
/**
* 连续序列帧方案的正式动作 prompt。
*
* 这是“图像序列帧”动作生成链路使用的正式 prompt
* 不属于默认描述文本层。
*/
export function buildImageSequencePrompt(
animation: string,
promptText: string,
frameCount: number,
useChromaKey: boolean,
) {
return [
`同一角色连续 ${frameCount} 帧动作序列,动作主题是 ${animation}`,
'固定机位,单人,全身,侧身朝右,保持同一套服装、发型、武器和体型。',
'帧间动作连续,姿态逐步推进,不要换人,不要跳变,不要多余物体。',
useChromaKey
? '纯绿色背景,无地面装饰,方便后期抠像。'
: '背景尽量纯净,避免复杂场景。',
promptText.trim(),
]
.filter(Boolean)
.join(' ');
}
/**
* 通用动作视频方案的正式动作 prompt。
*
* 输入的 promptText 是动作描述文本;
* 输出的是可以直接提交给动作模型的视频 prompt。
*
* 当前仓库里它主要服务于非 Ark 的动作视频链路,
* 以及某些保留的动作生成策略。
*/
export function buildNpcAnimationPrompt(options: {
animation: string;
promptText: string;
useChromaKey: boolean;
loop: boolean;
characterBriefText?: string;
actionTemplateId?: string;
}) {
const characterBrief = buildCompactAnimationCharacterBrief(
options.characterBriefText ?? '',
);
const actionDetailText = sanitizeAnimationPromptText(options.promptText, 140);
const loopRule = options.loop
? '这是循环动作,直接进入动作循环中段,不要开场静止站桩,不要把主参考图原样作为第一帧。'
: options.animation === 'die'
? '这是死亡终结动作,首帧参考主图角色形象即可,尾帧停在死亡结束姿态,不要回到主图形象。'
: '这是非循环动作,首帧和尾帧都要回到参考主图角色形象,中段完成动作变化。';
if (options.actionTemplateId) {
return [
buildVideoActionPrompt({
actionTemplate: getActionTemplateById(
options.actionTemplateId as Parameters<
typeof getActionTemplateById
>[0],
),
actionDetailText,
useChromaKey: options.useChromaKey,
characterBrief: characterBrief || `${options.animation} 动作角色`,
}),
loopRule,
]
.filter(Boolean)
.join(' ');
}
return [
`单人 NPC 全身动作视频,动作主题是 ${options.animation}`,
'角色固定为同一人,侧身朝右,镜头稳定,轮廓清晰,武器不可丢失。',
'动作连贯,避免服装、发型、面部、武器随机漂移。',
options.useChromaKey
? '背景为纯绿色绿幕,无其他人物和场景元素,方便后期抠像。'
: '背景简洁纯净,无复杂场景。',
characterBrief ? `角色设定:${characterBrief}` : '',
actionDetailText,
loopRule,
]
.filter(Boolean)
.join(' ');
}
/**
* Ark 图生视频动作链路的正式动作 prompt。
*
* 当前自定义世界角色资产工坊的主动作生成流程,
* 最终会走到这个 builder。它会在共享模板的基础上
* 叠加 Ark 所需的首帧 / 尾帧约束与动作英文名约束。
*/
export function buildArkCharacterAnimationPrompt(options: {
animation: string;
promptText: string;
useChromaKey: boolean;
loop: boolean;
characterBriefText?: string;
actionTemplateId?: string;
}) {
const normalizedAnimationName =
options.animation.trim().replace(/\s+/gu, '_') || 'idle';
const characterBrief = buildCompactAnimationCharacterBrief(
options.characterBriefText ?? '',
);
const actionDetailText = sanitizeAnimationPromptText(options.promptText, 140);
const frameRule = options.loop
? '首帧严格使用图片1尾帧严格使用图片2循环动作必须自然闭环不要静止开场。'
: '首帧严格使用图片1尾帧严格使用图片2中段完成完整动作变化收束干净。';
if (options.actionTemplateId) {
return [
buildVideoActionPrompt({
actionTemplate: getActionTemplateById(
options.actionTemplateId as Parameters<typeof getActionTemplateById>[0],
),
actionDetailText,
useChromaKey: options.useChromaKey,
characterBrief: characterBrief || `${normalizedAnimationName} action role`,
}),
`动作英文名:${normalizedAnimationName}`,
frameRule,
]
.filter(Boolean)
.join(' ');
}
return [
`单人 NPC 全身动作视频,动作英文名是 ${normalizedAnimationName}`,
'角色固定为图片1和图片2中的同一人侧身朝右镜头稳定轮廓清晰武器不可丢失。',
'动作连贯,避免服装、发型、面部、武器随机漂移,不要多角色,不要镜头切换。',
options.useChromaKey
? '背景为纯绿色绿幕,无其他人物和场景元素,方便后期抠像。'
: '背景简洁纯净,无复杂场景。',
characterBrief ? `角色设定:${characterBrief}` : '',
actionDetailText ? `动作细节:${actionDetailText}` : '',
frameRule,
]
.filter(Boolean)
.join(' ');
}
/**
* 当正式动作 prompt 触发审核或兼容问题时的保守兜底动作 prompt。
*
* 这条链路是正式动作生成的降级方案,不参与默认描述文本生成。
*/
export function buildFallbackModerationSafeAnimationPrompt(options: {
animation: string;
loop: boolean;
useChromaKey: boolean;
}) {
return [
`单人全身角色动作视频,动作主题是 ${options.animation}`,
'角色固定为同一人,右向斜侧身,镜头稳定,轮廓清楚。',
options.loop
? '循环动作直接进入稳定循环,不要静止开场,不要定格首帧。'
: '非循环动作首尾回到角色标准站姿,中段完成动作变化。',
options.useChromaKey
? '背景为纯绿色绿幕,无其他人物和场景元素。'
: '背景简洁纯净。',
]
.filter(Boolean)
.join(' ');
}