This commit is contained in:
2026-04-28 19:36:39 +08:00
parent a9febe7678
commit f0471a4f8d
206 changed files with 18456 additions and 10133 deletions

View File

@@ -9,6 +9,7 @@ import type {
NpcRecruitDialogueRequest,
PlainTextResponse,
} from '../../packages/shared/src/contracts/rpgRuntimeChat';
import type { RuntimeStoryAiRequest } from '../../packages/shared/src/contracts/rpgRuntimeStoryState';
import type {
CustomWorldGenerationProgress,
GenerateCustomWorldProfileInput,
@@ -32,21 +33,13 @@ import type {
TextStreamOptions,
} from './aiTypes';
import { fetchWithApiAuth, requestJson } from './apiClient';
import { type CharacterChatTargetStatus } from './characterChatPrompt';
import { type CharacterChatTargetStatus } from './rpgRuntimeChatTypes';
import { parseLineListContent } from './llmParsers';
const RUNTIME_API_BASE = '/api/runtime';
type LegacyAiModule = typeof import('./ai');
let legacyAiModulePromise: Promise<LegacyAiModule> | null = null;
async function loadLegacyAiModule() {
if (!legacyAiModulePromise) {
legacyAiModulePromise = import('./ai');
}
return legacyAiModulePromise;
function getRuntimeSessionIdFromContext(context: StoryGenerationContext) {
return context.runtimeSessionId?.trim() || undefined;
}
async function requestPlainText(
@@ -169,29 +162,27 @@ export async function generateInitialStory(
context: StoryGenerationContext,
requestOptions: StoryRequestOptions = {},
): Promise<AIResponse> {
if (typeof window === 'undefined') {
const aiClient = await loadLegacyAiModule();
return aiClient.generateInitialStory(
world,
character,
monsters,
context,
requestOptions,
);
}
const sessionId = getRuntimeSessionIdFromContext(context);
const payload: RuntimeStoryAiRequest | Record<string, unknown> = sessionId
? {
sessionId,
clientVersion: context.runtimeActionVersion,
requestOptions,
}
: {
worldType: world,
character,
monsters,
context,
requestOptions,
};
return requestJson<AIResponse>(
`${RUNTIME_API_BASE}/story/initial`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
worldType: world,
character,
monsters,
context,
requestOptions,
}),
body: JSON.stringify(payload),
},
'剧情开局生成失败',
);
@@ -206,25 +197,18 @@ export async function generateNextStep(
context: StoryGenerationContext,
requestOptions: StoryRequestOptions = {},
): Promise<AIResponse> {
if (typeof window === 'undefined') {
const aiClient = await loadLegacyAiModule();
return aiClient.generateNextStep(
world,
character,
monsters,
history,
choice,
context,
requestOptions,
);
}
return requestJson<AIResponse>(
`${RUNTIME_API_BASE}/story/continue`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
const sessionId = getRuntimeSessionIdFromContext(context);
const payload: RuntimeStoryAiRequest | Record<string, unknown> = sessionId
? {
sessionId,
clientVersion: context.runtimeActionVersion,
choice,
lastFunctionId: context.lastFunctionId,
observeSignsRequested: context.observeSignsRequested,
recentActionResult: context.recentActionResult,
requestOptions,
}
: {
worldType: world,
character,
monsters,
@@ -232,7 +216,14 @@ export async function generateNextStep(
choice,
context,
requestOptions,
}),
};
return requestJson<AIResponse>(
`${RUNTIME_API_BASE}/story/continue`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
},
'剧情续写失败',
);
@@ -248,30 +239,25 @@ export async function generateCharacterPanelChatSuggestions(
conversationSummary: string,
targetStatus: CharacterChatTargetStatus,
) {
if (typeof window === 'undefined') {
const aiClient = await loadLegacyAiModule();
return aiClient.generateCharacterPanelChatSuggestions(
world,
playerCharacter,
targetCharacter,
storyHistory,
context,
conversationHistory,
conversationSummary,
targetStatus,
);
}
const payload = {
worldType: world,
playerCharacter,
targetCharacter,
storyHistory,
context,
conversationHistory,
conversationSummary,
targetStatus,
} satisfies CharacterChatSuggestionsRequest;
const sessionId = getRuntimeSessionIdFromContext(context);
const payload = sessionId
? ({
sessionId,
targetCharacter,
conversationHistory,
conversationSummary,
targetStatus,
} satisfies CharacterChatSuggestionsRequest)
: ({
worldType: world,
playerCharacter,
targetCharacter,
storyHistory,
context,
conversationHistory,
conversationSummary,
targetStatus,
} satisfies CharacterChatSuggestionsRequest);
const { text } = await requestPlainText(
`${RUNTIME_API_BASE}/chat/character/suggestions`,
@@ -291,30 +277,25 @@ export async function generateCharacterPanelChatSummary(
previousSummary: string,
targetStatus: CharacterChatTargetStatus,
) {
if (typeof window === 'undefined') {
const aiClient = await loadLegacyAiModule();
return aiClient.generateCharacterPanelChatSummary(
world,
playerCharacter,
targetCharacter,
storyHistory,
context,
conversationHistory,
previousSummary,
targetStatus,
);
}
const payload = {
worldType: world,
playerCharacter,
targetCharacter,
storyHistory,
context,
conversationHistory,
previousSummary,
targetStatus,
} satisfies CharacterChatSummaryRequest;
const sessionId = getRuntimeSessionIdFromContext(context);
const payload = sessionId
? ({
sessionId,
targetCharacter,
conversationHistory,
previousSummary,
targetStatus,
} satisfies CharacterChatSummaryRequest)
: ({
worldType: world,
playerCharacter,
targetCharacter,
storyHistory,
context,
conversationHistory,
previousSummary,
targetStatus,
} satisfies CharacterChatSummaryRequest);
const { text } = await requestPlainText(
`${RUNTIME_API_BASE}/chat/character/summary`,
@@ -336,33 +317,27 @@ export async function streamCharacterPanelChatReply(
targetStatus: CharacterChatTargetStatus,
options: TextStreamOptions = {},
) {
if (typeof window === 'undefined') {
const aiClient = await loadLegacyAiModule();
return aiClient.streamCharacterPanelChatReply(
world,
playerCharacter,
targetCharacter,
storyHistory,
context,
conversationHistory,
conversationSummary,
playerMessage,
targetStatus,
options,
);
}
const payload = {
worldType: world,
playerCharacter,
targetCharacter,
storyHistory,
context,
conversationHistory,
conversationSummary,
playerMessage,
targetStatus,
} satisfies CharacterChatReplyRequest;
const sessionId = getRuntimeSessionIdFromContext(context);
const payload = sessionId
? ({
sessionId,
targetCharacter,
conversationHistory,
conversationSummary,
playerMessage,
targetStatus,
} satisfies CharacterChatReplyRequest)
: ({
worldType: world,
playerCharacter,
targetCharacter,
storyHistory,
context,
conversationHistory,
conversationSummary,
playerMessage,
targetStatus,
} satisfies CharacterChatReplyRequest);
const reply = await requestPlainTextStream(
`${RUNTIME_API_BASE}/chat/character/reply/stream`,
@@ -383,31 +358,24 @@ export async function streamNpcChatDialogue(
resultSummary: string,
options: TextStreamOptions = {},
) {
if (typeof window === 'undefined') {
const aiClient = await loadLegacyAiModule();
return aiClient.streamNpcChatDialogue(
world,
character,
encounter,
monsters,
history,
context,
topic,
resultSummary,
options,
);
}
const payload = {
worldType: world,
character,
encounter,
monsters,
history,
context,
topic,
resultSummary,
} satisfies NpcChatDialogueRequest;
const sessionId = getRuntimeSessionIdFromContext(context);
const payload = sessionId
? ({
sessionId,
encounter,
topic,
resultSummary,
} satisfies NpcChatDialogueRequest)
: ({
worldType: world,
character,
encounter,
monsters,
history,
context,
topic,
resultSummary,
} satisfies NpcChatDialogueRequest);
const dialogue = await requestPlainTextStream(
`${RUNTIME_API_BASE}/chat/npc/dialogue/stream`,
@@ -442,14 +410,9 @@ export async function streamNpcChatTurn(
npcInitiatesConversation?: boolean;
} = {},
) {
const payload = {
worldType: world,
character,
player: character,
const sessionId = getRuntimeSessionIdFromContext(context);
const commonChatPayload = {
encounter,
monsters,
history,
context,
conversationHistory: conversationHistory ?? [],
dialogue: conversationHistory ?? [],
playerMessage,
@@ -457,7 +420,7 @@ export async function streamNpcChatTurn(
npcInitiatesConversation: options.npcInitiatesConversation ?? false,
questOfferContext: options.questOfferContext
? {
state: options.questOfferContext.state,
state: sessionId ? {} : options.questOfferContext.state,
encounter,
turnCount: options.questOfferContext.turnCount,
}
@@ -471,7 +434,21 @@ export async function streamNpcChatTurn(
})),
}
: null,
} satisfies NpcChatTurnRequest;
};
const payload = sessionId
? ({
sessionId,
...commonChatPayload,
} satisfies NpcChatTurnRequest)
: ({
worldType: world,
character,
player: character,
monsters,
history,
context,
...commonChatPayload,
} satisfies NpcChatTurnRequest);
const response = await fetchWithApiAuth(
`${RUNTIME_API_BASE}/chat/npc/turn/stream`,
@@ -570,31 +547,24 @@ export async function streamNpcRecruitDialogue(
recruitSummary: string,
options: TextStreamOptions = {},
) {
if (typeof window === 'undefined') {
const aiClient = await loadLegacyAiModule();
return aiClient.streamNpcRecruitDialogue(
world,
character,
encounter,
monsters,
history,
context,
invitationText,
recruitSummary,
options,
);
}
const payload = {
worldType: world,
character,
encounter,
monsters,
history,
context,
invitationText,
recruitSummary,
} satisfies NpcRecruitDialogueRequest;
const sessionId = getRuntimeSessionIdFromContext(context);
const payload = sessionId
? ({
sessionId,
encounter,
invitationText,
recruitSummary,
} satisfies NpcRecruitDialogueRequest)
: ({
worldType: world,
character,
encounter,
monsters,
history,
context,
invitationText,
recruitSummary,
} satisfies NpcRecruitDialogueRequest);
const dialogue = await requestPlainTextStream(
`${RUNTIME_API_BASE}/chat/npc/recruit/stream`,