Rework story engine flow and reorganize project docs
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-06 23:19:00 +08:00
parent d678929064
commit ddcb5d5c8c
241 changed files with 19805 additions and 2478 deletions

View File

@@ -1,4 +1,4 @@
import { createSceneNpcMonstersFromEncounters } from '../data/hostileNpcs';
import { createSceneHostileNpcsFromEncounters } from '../data/hostileNpcs';
import {
buildEncounterFromSceneNpc,
getScenePresetById,
@@ -13,13 +13,17 @@ import {
AIResponse,
Character,
CharacterChatTurn,
CustomWorldCreatorIntent,
CustomWorldGenerationMode,
CustomWorldProfile,
Encounter,
SceneEncounterResult,
SceneMonster,
SceneHostileNpc,
SceneNpc,
StoryMoment,
StoryOption,
ThemePack,
WorldStoryGraph,
WorldType,
} from '../types';
import {
@@ -45,6 +49,8 @@ import {
CharacterChatTargetStatus,
} from './characterChatPrompt';
import {
buildCustomWorldActorNarrativeProfileBatchJsonRepairPrompt,
buildCustomWorldActorNarrativeProfileBatchPrompt,
buildCustomWorldFrameworkJsonRepairPrompt,
buildCustomWorldFrameworkPrompt,
buildCustomWorldLandmarkNetworkBatchJsonRepairPrompt,
@@ -57,6 +63,10 @@ import {
buildCustomWorldRoleOutlineBatchJsonRepairPrompt,
buildCustomWorldRoleOutlineBatchPrompt,
buildCustomWorldSceneImagePrompt,
buildCustomWorldStoryGraphJsonRepairPrompt,
buildCustomWorldStoryGraphPrompt,
buildCustomWorldThemePackJsonRepairPrompt,
buildCustomWorldThemePackPrompt,
type CustomWorldGenerationFramework,
type CustomWorldGenerationLandmarkOutline,
type CustomWorldGenerationRoleBatchStage,
@@ -73,6 +83,12 @@ import {
validateGeneratedCustomWorldProfile,
} from './customWorld';
import { buildExpandedCustomWorldProfile } from './customWorldBuilder';
import {
buildCustomWorldAnchorPackFromIntent,
buildCustomWorldCreatorIntentGenerationText,
deriveCustomWorldLockStateFromIntent,
hasMeaningfulCustomWorldCreatorIntent,
} from './customWorldCreatorIntent';
import {
CUSTOM_WORLD_REQUEST_TIMEOUT_MS as CLIENT_CUSTOM_WORLD_REQUEST_TIMEOUT_MS,
isLlmConnectivityError as isLlmConnectivityErrorFromClient,
@@ -94,6 +110,18 @@ import {
NPC_RECRUIT_DIALOGUE_SYSTEM_PROMPT,
SYSTEM_PROMPT,
} from './prompt';
import {
buildFallbackActorNarrativeProfile,
normalizeActorNarrativeProfile,
} from './storyEngine/actorNarrativeProfile';
import {
buildThemePackFromWorldProfile,
normalizeThemePack,
} from './storyEngine/themePack';
import {
buildFallbackWorldStoryGraph,
normalizeWorldStoryGraph,
} from './storyEngine/worldStoryGraph';
export type {
StoryGenerationContext,
@@ -169,6 +197,20 @@ const CUSTOM_WORLD_GENERATION_STAGE_DEFINITIONS = [
total: 1,
weight: 1,
},
{
id: 'theme-pack',
label: '题材适配层',
detail: '提炼制度词汇、禁忌词与命名范式。',
total: 1,
weight: 1,
},
{
id: 'story-graph',
label: '世界线程图谱',
detail: '补出明线、暗线、旧伤与意象母题。',
total: 1,
weight: 1,
},
{
id: 'playable-outline',
label: '可扮演角色骨架',
@@ -245,6 +287,18 @@ const CUSTOM_WORLD_GENERATION_STAGE_DEFINITIONS = [
),
),
},
{
id: 'playable-profile',
label: '可扮演角色叙事档案',
detail: '为可扮演角色生成首遇面具、当前压力和暗线钩子。',
total: MIN_CUSTOM_WORLD_PLAYABLE_NPC_COUNT,
weight: Math.max(
1,
Math.ceil(
MIN_CUSTOM_WORLD_PLAYABLE_NPC_COUNT / CUSTOM_WORLD_PLAYABLE_BATCH_SIZE,
),
),
},
{
id: 'story-narrative',
label: '场景角色叙事',
@@ -255,6 +309,18 @@ const CUSTOM_WORLD_GENERATION_STAGE_DEFINITIONS = [
Math.ceil(MIN_CUSTOM_WORLD_STORY_NPC_COUNT / CUSTOM_WORLD_STORY_BATCH_SIZE),
),
},
{
id: 'story-profile',
label: '场景角色叙事档案',
detail: '为场景角色生成首遇面具、当前压力和暗线钩子。',
total: MIN_CUSTOM_WORLD_STORY_NPC_COUNT,
weight: Math.max(
1,
Math.ceil(
MIN_CUSTOM_WORLD_STORY_NPC_COUNT / CUSTOM_WORLD_STORY_BATCH_SIZE,
),
),
},
{
id: 'story-dossier',
label: '场景角色档案',
@@ -305,6 +371,16 @@ export interface GenerateCustomWorldProfileOptions {
signal?: AbortSignal;
}
export interface GenerateCustomWorldProfileInput {
settingText: string;
creatorIntent?: CustomWorldCreatorIntent | null;
generationMode?: CustomWorldGenerationMode;
}
const FAST_CUSTOM_WORLD_PLAYABLE_COUNT = 3;
const FAST_CUSTOM_WORLD_STORY_COUNT = 8;
const FAST_CUSTOM_WORLD_LANDMARK_COUNT = 4;
class CustomWorldGenerationAbortedError extends Error {
constructor(message = '世界生成已中断。') {
super(message);
@@ -341,6 +417,60 @@ function normalizeApiErrorMessage(
return responseText;
}
function resolveCustomWorldGenerationInput(
input: string | GenerateCustomWorldProfileInput,
): {
settingText: string;
generationSeedText: string;
creatorIntent: CustomWorldCreatorIntent | null;
generationMode: CustomWorldGenerationMode;
} {
if (typeof input === 'string') {
return {
settingText: input.trim(),
generationSeedText: input.trim(),
creatorIntent: null as CustomWorldCreatorIntent | null,
generationMode: 'full' as CustomWorldGenerationMode,
};
}
const normalizedSettingText = input.settingText.trim();
const creatorIntent = input.creatorIntent ?? null;
const generationSeedText =
creatorIntent && hasMeaningfulCustomWorldCreatorIntent(creatorIntent)
? buildCustomWorldCreatorIntentGenerationText(creatorIntent)
: normalizedSettingText;
return {
settingText: normalizedSettingText,
generationSeedText: generationSeedText.trim(),
creatorIntent,
generationMode: input.generationMode === 'fast'
? ('fast' as const)
: ('full' as const),
};
}
function getCustomWorldGenerationTargets(
generationMode: CustomWorldGenerationMode,
) {
if (generationMode === 'fast') {
return {
playableCount: FAST_CUSTOM_WORLD_PLAYABLE_COUNT,
storyCount: FAST_CUSTOM_WORLD_STORY_COUNT,
landmarkCount: FAST_CUSTOM_WORLD_LANDMARK_COUNT,
generationStatus: 'key_only' as const,
};
}
return {
playableCount: MIN_CUSTOM_WORLD_PLAYABLE_NPC_COUNT,
storyCount: MIN_CUSTOM_WORLD_STORY_NPC_COUNT,
landmarkCount: MIN_CUSTOM_WORLD_LANDMARK_COUNT,
generationStatus: 'complete' as const,
};
}
function sanitizeJsonLikeText(text: string) {
const trimmed = text.trim();
if (!trimmed) {
@@ -493,6 +623,12 @@ function getCustomWorldGenerationStageIdForRoleExpansion(
return stage === 'narrative' ? 'story-narrative' : 'story-dossier';
}
function getCustomWorldGenerationStageIdForActorProfile(
roleType: CustomWorldGenerationRoleBatchType,
): CustomWorldGenerationStageId {
return roleType === 'playable' ? 'playable-profile' : 'story-profile';
}
function throwIfCustomWorldGenerationAborted(signal?: AbortSignal) {
if (!signal?.aborted) {
return;
@@ -952,6 +1088,154 @@ async function expandCustomWorldRoleEntries<
return mergedEntries;
}
async function generateCustomWorldThemePackWithAi(params: {
framework: CustomWorldGenerationFramework;
signal?: AbortSignal;
}) {
const { framework, signal } = params;
const fallback = buildThemePackFromWorldProfile({
...framework,
templateWorldType:
framework.templateWorldType === WorldType.XIANXIA
? WorldType.XIANXIA
: WorldType.WUXIA,
});
const raw = await requestCustomWorldJsonStage({
userPrompt: buildCustomWorldThemePackPrompt({ framework }),
debugLabel: 'custom-world-theme-pack',
repairPromptBuilder: (responseText) =>
buildCustomWorldThemePackJsonRepairPrompt({ responseText }),
repairDebugLabel: 'custom-world-theme-pack-json-repair',
emptyResponseMessage: '自定义世界 ThemePack 生成失败:模型没有返回有效内容。',
signal,
});
return normalizeThemePack(raw, fallback);
}
async function generateCustomWorldStoryGraphWithAi(params: {
framework: CustomWorldGenerationFramework;
themePack: ThemePack;
signal?: AbortSignal;
}) {
const { framework, themePack, signal } = params;
const profileSeed = buildExpandedCustomWorldProfile(
buildCustomWorldRawProfileFromFramework(framework),
framework.settingText,
);
const fallback = buildFallbackWorldStoryGraph(
{
...profileSeed,
themePack,
},
themePack,
);
const raw = await requestCustomWorldJsonStage({
userPrompt: buildCustomWorldStoryGraphPrompt({
framework,
themePack,
}),
debugLabel: 'custom-world-story-graph',
repairPromptBuilder: (responseText) =>
buildCustomWorldStoryGraphJsonRepairPrompt({ responseText }),
repairDebugLabel: 'custom-world-story-graph-json-repair',
emptyResponseMessage: '自定义世界 StoryGraph 生成失败:模型没有返回有效内容。',
signal,
});
return normalizeWorldStoryGraph(raw, fallback);
}
async function expandCustomWorldActorNarrativeProfiles<
T extends MergeableCustomWorldRoleEntry,
>(params: {
framework: CustomWorldGenerationFramework;
roleType: CustomWorldGenerationRoleBatchType;
baseEntries: T[];
batchSize: number;
themePack: ThemePack;
storyGraph: WorldStoryGraph;
reporter?: CustomWorldGenerationReporter;
signal?: AbortSignal;
}) {
const {
framework,
roleType,
baseEntries,
batchSize,
themePack,
storyGraph,
reporter = createCustomWorldGenerationReporter(),
signal,
} = params;
const roleBatchSource = baseEntries;
const roleLabel = roleType === 'playable' ? '可扮演角色' : '场景角色';
const stageId = getCustomWorldGenerationStageIdForActorProfile(roleType);
const plannedBatchCount = Math.max(1, Math.ceil(roleBatchSource.length / batchSize));
let mergedEntries = baseEntries.map((entry) => ({ ...entry })) as T[];
let processedCount = 0;
for (const [batchIndex, roleBatch] of chunkArray(roleBatchSource, batchSize).entries()) {
throwIfCustomWorldGenerationAborted(signal);
reporter.update(stageId, processedCount, {
phaseDetail: `正在补充${roleLabel}叙事档案,已完成 ${processedCount}/${roleBatchSource.length}`,
batchLabel: `${batchIndex + 1} / ${plannedBatchCount}`,
});
const stageRaw = await requestCustomWorldJsonStage({
userPrompt: buildCustomWorldActorNarrativeProfileBatchPrompt({
framework,
roleType,
roleBatch: roleBatch as Array<Record<string, unknown>>,
themePack,
storyGraph,
}),
debugLabel: `custom-world-${roleType}-actor-profile-batch-${batchIndex + 1}`,
repairPromptBuilder: (responseText) =>
buildCustomWorldActorNarrativeProfileBatchJsonRepairPrompt({
responseText,
roleType,
expectedNames: roleBatch.map((role) => getNamedRecordKey(role.name)),
}),
repairDebugLabel: `custom-world-${roleType}-actor-profile-batch-${batchIndex + 1}-json-repair`,
emptyResponseMessage: `自定义世界${roleLabel}叙事档案批次 ${batchIndex + 1} 生成失败:模型没有返回有效内容。`,
signal,
});
mergedEntries = mergeRoleBatchDetails(
mergedEntries,
toRecordArray(
stageRaw && typeof stageRaw === 'object'
? (stageRaw as Record<string, unknown>)[
roleType === 'playable' ? 'playableNpcs' : 'storyNpcs'
]
: [],
),
);
processedCount = Math.min(roleBatchSource.length, processedCount + roleBatch.length);
reporter.update(stageId, processedCount, {
phaseDetail: `正在补充${roleLabel}叙事档案,已完成 ${processedCount}/${roleBatchSource.length}`,
batchLabel: `${batchIndex + 1} / ${plannedBatchCount}`,
});
}
return mergedEntries.map((entry) => {
const item = entry as Record<string, unknown>;
const fallbackProfile = buildFallbackActorNarrativeProfile(
entry as unknown as CustomWorldProfile['storyNpcs'][number],
storyGraph,
themePack,
);
return {
...entry,
narrativeProfile: normalizeActorNarrativeProfile(
item.narrativeProfile,
fallbackProfile,
),
} as T;
});
}
async function parseCustomWorldStageResponseJson(params: {
responseText: string;
repairPrompt: string;
@@ -1060,7 +1344,7 @@ async function requestCustomWorldJsonStage(params: {
function buildFunctionContext(
worldType: WorldType,
character: Character,
monsters: SceneMonster[],
monsters: SceneHostileNpc[],
context: StoryGenerationContext,
): FunctionAvailabilityContext {
return {
@@ -1089,17 +1373,8 @@ function normalizeEncounterResult(
const kind = typeof item.kind === 'string' ? item.kind.trim() : '';
if (kind === 'monster') {
const rawMonsterIds = Array.isArray(item.monsterIds) ? item.monsterIds : [];
const fallbackHostileNpc =
scene?.npcs.find(
(npc: SceneNpc) =>
isHostileSceneNpc(npc) &&
rawMonsterIds.some(
(monsterId) =>
typeof monsterId === 'string' &&
npc.monsterPresetId === monsterId,
),
) ?? scene?.npcs.find((npc: SceneNpc) => isHostileSceneNpc(npc));
scene?.npcs.find((npc: SceneNpc) => isHostileSceneNpc(npc));
return fallbackHostileNpc
? { kind: 'npc', npcId: fallbackHostileNpc.id }
@@ -1128,7 +1403,7 @@ function normalizeEncounterResult(
function buildEncounterDrivenResolution(
worldType: WorldType,
inputMonsters: SceneMonster[],
inputMonsters: SceneHostileNpc[],
context: StoryGenerationContext,
encounter: SceneEncounterResult | undefined,
) {
@@ -1148,7 +1423,7 @@ function buildEncounterDrivenResolution(
);
if (sceneNpc?.monsterPresetId && isHostileSceneNpc(sceneNpc)) {
return {
monsters: createSceneNpcMonstersFromEncounters(
monsters: createSceneHostileNpcsFromEncounters(
worldType,
[buildEncounterFromSceneNpc(sceneNpc, context.playerX)],
context.playerX,
@@ -1176,7 +1451,7 @@ function resolveOptionsFromFunctionIds(
items: RawOptionItem[],
worldType: WorldType,
character: Character,
monsters: SceneMonster[],
monsters: SceneHostileNpc[],
context: StoryGenerationContext,
): StoryOption[] {
const functionContext = buildFunctionContext(
@@ -1305,7 +1580,7 @@ function resolveOptionsFromOptionCatalog(
function getFallbackOptions(
worldType: WorldType,
character: Character,
monsters: SceneMonster[],
monsters: SceneHostileNpc[],
context: StoryGenerationContext,
): StoryOption[] {
const functionContext = buildFunctionContext(
@@ -1329,7 +1604,7 @@ function getFallbackOptions(
function buildOfflineResponse(
world: WorldType,
character: Character,
monsters: SceneMonster[],
monsters: SceneHostileNpc[],
context: StoryGenerationContext,
choice?: string,
requestOptions: StoryRequestOptions = {},
@@ -1391,7 +1666,7 @@ function normalizeResponse(
raw: unknown,
worldType: WorldType,
character: Character,
monsters: SceneMonster[],
monsters: SceneHostileNpc[],
context: StoryGenerationContext,
requestOptions: StoryRequestOptions = {},
): AIResponse {
@@ -1483,7 +1758,7 @@ async function requestCompletion(
userPrompt: string,
worldType: WorldType,
character: Character,
monsters: SceneMonster[],
monsters: SceneHostileNpc[],
context: StoryGenerationContext,
requestOptions: StoryRequestOptions = {},
): Promise<AIResponse> {
@@ -1584,10 +1859,16 @@ export async function generateCustomWorldSceneImage({
}
export async function generateCustomWorldProfile(
settingText: string,
input: string | GenerateCustomWorldProfileInput,
options: GenerateCustomWorldProfileOptions = {},
): Promise<CustomWorldProfile> {
const normalizedSettingText = settingText.trim();
const {
settingText: normalizedSettingText,
generationSeedText,
creatorIntent,
generationMode,
} = resolveCustomWorldGenerationInput(input);
const generationTargets = getCustomWorldGenerationTargets(generationMode);
const reporter = createCustomWorldGenerationReporter(options.onProgress);
const signal = options.signal;
@@ -1597,7 +1878,7 @@ export async function generateCustomWorldProfile(
phaseDetail: '正在解析你的设定文本,准备搭建世界框架。',
});
const frameworkRaw = await requestCustomWorldJsonStage({
userPrompt: buildCustomWorldFrameworkPrompt(normalizedSettingText),
userPrompt: buildCustomWorldFrameworkPrompt(generationSeedText),
debugLabel: 'custom-world-framework',
repairPromptBuilder: buildCustomWorldFrameworkJsonRepairPrompt,
repairDebugLabel: 'custom-world-framework-json-repair',
@@ -1607,7 +1888,7 @@ export async function generateCustomWorldProfile(
const frameworkBase = {
...normalizeCustomWorldGenerationFramework(
frameworkRaw,
normalizedSettingText,
generationSeedText,
),
playableNpcs: [],
storyNpcs: [],
@@ -1616,6 +1897,16 @@ export async function generateCustomWorldProfile(
reporter.complete('framework', {
phaseDetail: `世界框架已确定,基础模板锚定为${frameworkBase.templateWorldType === WorldType.WUXIA ? '武侠' : '仙侠'}`,
});
reporter.begin('theme-pack', {
phaseDetail: '正在提炼题材适配层词汇与命名范式。',
});
const themePack = await generateCustomWorldThemePackWithAi({
framework: frameworkBase,
signal,
});
reporter.complete('theme-pack', {
phaseDetail: `题材适配层已完成,当前题材包为“${themePack.displayName}”。`,
});
reporter.begin('playable-outline', {
phaseDetail: '正在生成可扮演角色骨架。',
@@ -1624,7 +1915,7 @@ export async function generateCustomWorldProfile(
(await generateCustomWorldRoleOutlineEntries({
framework: frameworkBase,
roleType: 'playable',
totalCount: MIN_CUSTOM_WORLD_PLAYABLE_NPC_COUNT,
totalCount: generationTargets.playableCount,
batchSize: CUSTOM_WORLD_FRAMEWORK_PLAYABLE_OUTLINE_BATCH_SIZE,
reporter,
signal,
@@ -1644,7 +1935,7 @@ export async function generateCustomWorldProfile(
(await generateCustomWorldRoleOutlineEntries({
framework: frameworkWithPlayable,
roleType: 'story',
totalCount: MIN_CUSTOM_WORLD_STORY_NPC_COUNT,
totalCount: generationTargets.storyCount,
batchSize: CUSTOM_WORLD_FRAMEWORK_STORY_OUTLINE_BATCH_SIZE,
reporter,
signal,
@@ -1663,7 +1954,7 @@ export async function generateCustomWorldProfile(
const landmarkSeeds =
(await generateCustomWorldLandmarkSeedEntries({
framework: frameworkWithStory,
totalCount: MIN_CUSTOM_WORLD_LANDMARK_COUNT,
totalCount: generationTargets.landmarkCount,
batchSize: CUSTOM_WORLD_FRAMEWORK_LANDMARK_SEED_BATCH_SIZE,
reporter,
signal,
@@ -1696,7 +1987,20 @@ export async function generateCustomWorldProfile(
...frameworkWithStory,
landmarks,
} satisfies CustomWorldGenerationFramework;
validateCustomWorldGenerationFramework(framework);
if (generationMode === 'full') {
validateCustomWorldGenerationFramework(framework);
}
reporter.begin('story-graph', {
phaseDetail: '正在生成世界线程、旧伤与意象母题。',
});
const storyGraph = await generateCustomWorldStoryGraphWithAi({
framework,
themePack,
signal,
});
reporter.complete('story-graph', {
phaseDetail: `世界线程图谱已完成,当前可见线程 ${storyGraph.visibleThreads.length} 条,暗线 ${storyGraph.hiddenThreads.length} 条。`,
});
const baseRawProfile = buildCustomWorldRawProfileFromFramework(framework);
reporter.begin('playable-narrative', {
@@ -1722,6 +2026,52 @@ export async function generateCustomWorldProfile(
reporter,
signal,
});
const profileSeed = buildExpandedCustomWorldProfile(
{
...baseRawProfile,
playableNpcs: mergedPlayableNpcs,
storyNpcs: mergedStoryNpcs,
themePack,
storyGraph,
creatorIntent,
anchorPack: buildCustomWorldAnchorPackFromIntent(creatorIntent),
generationMode,
generationStatus: generationTargets.generationStatus,
},
generationSeedText,
);
reporter.begin('playable-profile', {
phaseDetail: '正在补充可扮演角色的叙事档案。',
});
const playableNpcsWithNarrativeProfile = await expandCustomWorldActorNarrativeProfiles({
framework,
roleType: 'playable',
baseEntries: profileSeed.playableNpcs.map((npc) => ({ ...npc })),
batchSize: CUSTOM_WORLD_PLAYABLE_BATCH_SIZE,
themePack,
storyGraph,
reporter,
signal,
});
reporter.complete('playable-profile', {
phaseDetail: `可扮演角色叙事档案已完成,共 ${playableNpcsWithNarrativeProfile.length} 名。`,
});
reporter.begin('story-profile', {
phaseDetail: '正在补充场景角色的叙事档案。',
});
const storyNpcsWithNarrativeProfile = await expandCustomWorldActorNarrativeProfiles({
framework,
roleType: 'story',
baseEntries: profileSeed.storyNpcs.map((npc) => ({ ...npc })),
batchSize: CUSTOM_WORLD_STORY_BATCH_SIZE,
themePack,
storyGraph,
reporter,
signal,
});
reporter.complete('story-profile', {
phaseDetail: `场景角色叙事档案已完成,共 ${storyNpcsWithNarrativeProfile.length} 名。`,
});
reporter.begin('finalize', {
phaseDetail: '正在归档世界并做完整性校验。',
@@ -1730,17 +2080,33 @@ export async function generateCustomWorldProfile(
const profile = buildExpandedCustomWorldProfile(
{
...baseRawProfile,
playableNpcs: mergedPlayableNpcs,
storyNpcs: mergedStoryNpcs,
playableNpcs: playableNpcsWithNarrativeProfile,
storyNpcs: storyNpcsWithNarrativeProfile,
themePack,
storyGraph,
creatorIntent,
anchorPack: buildCustomWorldAnchorPackFromIntent(creatorIntent),
generationMode,
generationStatus: generationTargets.generationStatus,
},
normalizedSettingText,
generationSeedText,
);
validateGeneratedCustomWorldProfile(profile);
if (generationMode === 'full') {
validateGeneratedCustomWorldProfile(profile);
}
reporter.complete('finalize', {
phaseDetail: `世界“${profile.name}”已完成归档。`,
});
return {
...profile,
settingText: normalizedSettingText || profile.settingText,
creatorIntent,
anchorPack:
profile.anchorPack ?? buildCustomWorldAnchorPackFromIntent(creatorIntent),
lockState:
profile.lockState ?? deriveCustomWorldLockStateFromIntent(creatorIntent),
generationMode,
generationStatus: generationTargets.generationStatus,
items: [],
};
} catch (error) {
@@ -1904,7 +2270,7 @@ export async function generateCharacterPanelChatSummary(
export async function generateInitialStory(
world: WorldType,
character: Character,
monsters: SceneMonster[],
monsters: SceneHostileNpc[],
context: StoryGenerationContext,
requestOptions: StoryRequestOptions = {},
): Promise<AIResponse> {
@@ -1944,7 +2310,7 @@ export async function generateInitialStory(
export async function generateNextStep(
world: WorldType,
character: Character,
monsters: SceneMonster[],
monsters: SceneHostileNpc[],
history: StoryMoment[],
choice: string,
context: StoryGenerationContext,
@@ -1987,7 +2353,7 @@ export async function streamNpcChatDialogue(
world: WorldType,
character: Character,
encounter: Encounter,
monsters: SceneMonster[],
monsters: SceneHostileNpc[],
history: StoryMoment[],
context: StoryGenerationContext,
topic: string,
@@ -2028,7 +2394,7 @@ export async function streamNpcRecruitDialogue(
world: WorldType,
character: Character,
encounter: Encounter,
monsters: SceneMonster[],
monsters: SceneHostileNpc[],
history: StoryMoment[],
context: StoryGenerationContext,
invitationText: string,