@@ -3,6 +3,7 @@ import type {
|
||||
CharacterChatSuggestionsRequest,
|
||||
CharacterChatSummaryRequest,
|
||||
NpcChatDialogueRequest,
|
||||
NpcChatTurnRequest,
|
||||
NpcRecruitDialogueRequest,
|
||||
} from '../../../../packages/shared/src/contracts/story.js';
|
||||
|
||||
@@ -48,6 +49,16 @@ export const NPC_RECRUIT_DIALOGUE_SYSTEM_PROMPT = `你是角色扮演 RPG 的招
|
||||
- 不允许出现“我不能答应”“我还没想好”“再让我考虑”“暂时不行”“以后再说”这类拒绝或拖延表述。
|
||||
- 最后一行必须由对方明确答应加入队伍。`;
|
||||
|
||||
export const NPC_CHAT_TURN_REPLY_SYSTEM_PROMPT = `你是角色扮演 RPG 里的当前 NPC。
|
||||
你只输出这名 NPC 此刻会对玩家说的一轮回复。
|
||||
只输出纯中文口语回复正文,不要输出角色名、引号、旁白、动作描写、Markdown、JSON 或解释。
|
||||
回复长度控制在 1 到 3 句,必须紧接玩家刚说的话,自然推进气氛、情报或关系。`;
|
||||
|
||||
export const NPC_CHAT_TURN_SUGGESTION_SYSTEM_PROMPT = `你要为玩家生成下一轮可直接点击的 3 条聊天续写候选。
|
||||
只输出纯文本,共 3 行,每行 1 条。
|
||||
不要加编号、项目符号、Markdown、JSON 或额外说明。
|
||||
三条候选必须明显不同,分别体现继续追问、表达态度、轻微拉近关系这三种不同方向。`;
|
||||
|
||||
function asRecord(value: unknown): JsonRecord | null {
|
||||
return value && typeof value === 'object' && !Array.isArray(value)
|
||||
? (value as JsonRecord)
|
||||
@@ -143,6 +154,41 @@ function describeConversationHistory(history: unknown) {
|
||||
: '聊天记录:暂无。';
|
||||
}
|
||||
|
||||
function describeNpcConversationHistory(history: unknown, npcName: string) {
|
||||
if (!Array.isArray(history) || history.length === 0) {
|
||||
return '当前聊天记录:暂无。';
|
||||
}
|
||||
|
||||
const lines = history
|
||||
.slice(-10)
|
||||
.map((item) => {
|
||||
const record = asRecord(item);
|
||||
const speaker = readString(record?.speaker);
|
||||
const speakerName = readString(record?.speakerName);
|
||||
const text = readString(record?.text);
|
||||
if (!text) return null;
|
||||
|
||||
if (speaker === 'player') {
|
||||
return `- 玩家:${text}`;
|
||||
}
|
||||
|
||||
if (speaker === 'npc') {
|
||||
return `- ${speakerName ?? npcName}:${text}`;
|
||||
}
|
||||
|
||||
if (speaker === 'system') {
|
||||
return `- 系统提示:${text}`;
|
||||
}
|
||||
|
||||
return `- ${speakerName ?? '同伴'}:${text}`;
|
||||
})
|
||||
.filter((item): item is string => Boolean(item));
|
||||
|
||||
return lines.length > 0
|
||||
? ['当前聊天记录:', ...lines].join('\n')
|
||||
: '当前聊天记录:暂无。';
|
||||
}
|
||||
|
||||
function describeSceneContext(context: unknown) {
|
||||
const record = asRecord(context);
|
||||
const sceneName = readString(record?.sceneName) ?? '当前区域';
|
||||
@@ -370,3 +416,40 @@ export function buildNpcRecruitDialoguePrompt(
|
||||
.filter(Boolean)
|
||||
.join('\n\n');
|
||||
}
|
||||
|
||||
export function buildNpcChatTurnReplyPrompt(
|
||||
payload: NpcChatTurnRequest,
|
||||
) {
|
||||
const encounter = describeEncounter(payload.encounter);
|
||||
const npcState = asRecord(payload.npcState);
|
||||
const affinity = readNumber(npcState?.affinity, 0);
|
||||
const chattedCount = readNumber(npcState?.chattedCount, 0);
|
||||
|
||||
return [
|
||||
buildNpcDialoguePromptBase(payload),
|
||||
describeNpcConversationHistory(payload.conversationHistory, encounter.npcName),
|
||||
`当前关系值:${affinity}`,
|
||||
`已聊天轮次:${chattedCount}`,
|
||||
`玩家刚刚说:${payload.playerMessage}`,
|
||||
`现在请只写 ${encounter.npcName} 这一轮会回复玩家的话。`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n\n');
|
||||
}
|
||||
|
||||
export function buildNpcChatTurnSuggestionPrompt(
|
||||
payload: NpcChatTurnRequest,
|
||||
npcReply: string,
|
||||
) {
|
||||
const encounter = describeEncounter(payload.encounter);
|
||||
|
||||
return [
|
||||
buildNpcDialoguePromptBase(payload),
|
||||
describeNpcConversationHistory(payload.conversationHistory, encounter.npcName),
|
||||
`玩家刚刚说:${payload.playerMessage}`,
|
||||
`NPC 刚刚回复:${npcReply}`,
|
||||
`请围绕刚刚这轮对话,为玩家生成 3 条可以继续和 ${encounter.npcName} 聊下去的中文短句候选。`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n\n');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user