创作数据流程收束

This commit is contained in:
2026-04-21 09:44:17 +08:00
parent effe0355bd
commit 3614e1f5a2
93 changed files with 1794 additions and 8651 deletions

View File

@@ -10,11 +10,7 @@ import type {
SendCustomWorldAgentMessageRequest,
} from '../../packages/shared/src/contracts/customWorldAgent';
import type {
AnswerCustomWorldSessionQuestionRequest,
CreateCustomWorldSessionRequest,
CustomWorldGenerationProgress,
CustomWorldSessionRecord,
CustomWorldSessionSummary,
GenerateCustomWorldProfileInput,
GenerateCustomWorldProfileOptions,
} from '../../packages/shared/src/contracts/runtime';
@@ -357,190 +353,8 @@ export async function generateCustomWorldProfile(
input: GenerateCustomWorldProfileInput | string,
options: GenerateCustomWorldProfileOptions = {},
): Promise<CustomWorldProfile> {
const normalizedInput =
typeof input === 'string'
? {
settingText: input,
creatorIntent: null,
generationMode: 'full' as const,
}
: {
settingText: input.settingText,
creatorIntent: input.creatorIntent ?? null,
generationMode:
input.generationMode === 'fast'
? ('fast' as const)
: ('full' as const),
};
const session = await createCustomWorldSession({
settingText: normalizedInput.settingText,
creatorIntent: normalizedInput.creatorIntent as Record<
string,
unknown
> | null,
generationMode: normalizedInput.generationMode,
});
const fallbackAnswerMap: Record<string, string> = {
world_hook:
typeof normalizedInput.creatorIntent?.worldHook === 'string' &&
normalizedInput.creatorIntent.worldHook.trim()
? normalizedInput.creatorIntent.worldHook.trim()
: normalizedInput.settingText.trim().slice(0, 120) ||
'这是一个围绕失衡秩序展开的世界。',
player_premise:
typeof normalizedInput.creatorIntent?.playerPremise === 'string' &&
normalizedInput.creatorIntent.playerPremise.trim()
? normalizedInput.creatorIntent.playerPremise.trim()
: '玩家是一名被卷入局势中心的行动者。',
opening_situation:
typeof normalizedInput.creatorIntent?.openingSituation === 'string' &&
normalizedInput.creatorIntent.openingSituation.trim()
? normalizedInput.creatorIntent.openingSituation.trim()
: '故事开局时,玩家正身处风暴边缘,必须立刻判断立场与风险。',
core_conflict:
Array.isArray(normalizedInput.creatorIntent?.coreConflicts) &&
normalizedInput.creatorIntent.coreConflicts.length > 0
? normalizedInput.creatorIntent.coreConflicts
.map((item) => (typeof item === 'string' ? item.trim() : ''))
.filter(Boolean)
.join('')
: '旧秩序与新威胁正在同时逼近,各方都在争夺主动权。',
};
for (const question of session.questions ?? []) {
if (question.answer?.trim()) {
continue;
}
const answer =
fallbackAnswerMap[question.id] || normalizedInput.settingText.trim();
await answerCustomWorldSessionQuestion(session.sessionId, {
questionId: question.id,
answer,
});
}
return streamCustomWorldSessionGeneration(session.sessionId, options);
}
export async function streamCustomWorldSessionGeneration(
sessionId: string,
options: GenerateCustomWorldProfileOptions = {},
): Promise<CustomWorldProfile> {
const response = await fetchWithApiAuth(
`${RUNTIME_API_BASE}/custom-world/sessions/${encodeURIComponent(sessionId)}/generate/stream`,
{
method: 'GET',
signal: options.signal,
},
);
if (!response.ok) {
const responseText = await response.text();
throw new Error(parseApiErrorMessage(responseText, '生成自定义世界失败'));
}
if (!response.body) {
throw new Error('自定义世界生成流不可用');
}
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let buffer = '';
let latestProfile: Record<string, unknown> | null = null;
for (;;) {
const { done, value } = await reader.read();
if (done) {
break;
}
buffer += decoder.decode(value, { stream: true });
while (buffer.includes('\n\n')) {
const boundary = buffer.indexOf('\n\n');
const eventBlock = buffer.slice(0, boundary);
buffer = buffer.slice(boundary + 2);
let eventName = '';
for (const rawLine of eventBlock.split(/\r?\n/u)) {
const line = rawLine.trim();
if (!line) {
continue;
}
if (line.startsWith('event:')) {
eventName = line.slice(6).trim();
continue;
}
if (!line.startsWith('data:')) {
continue;
}
const payloadText = line.slice(5).trim();
if (!payloadText) {
continue;
}
const payload = JSON.parse(payloadText) as Record<string, unknown>;
if (eventName === 'progress') {
if (
typeof payload.phaseId === 'string' &&
typeof payload.phaseLabel === 'string' &&
typeof payload.phaseDetail === 'string' &&
typeof payload.overallProgress === 'number' &&
Array.isArray(payload.steps)
) {
options.onProgress?.(
payload as unknown as CustomWorldGenerationProgress,
);
} else {
options.onProgress?.({
phaseId: 'finalize',
phaseLabel:
typeof payload.phase === 'string'
? payload.phase
: 'generating',
phaseDetail:
typeof payload.phase === 'string'
? payload.phase
: 'generating',
overallProgress:
typeof payload.progress === 'number'
? payload.progress / 100
: 0,
completedWeight:
typeof payload.progress === 'number' ? payload.progress : 0,
totalWeight: 100,
elapsedMs: 0,
estimatedRemainingMs: null,
activeStepIndex: 0,
steps: [],
});
}
}
if (
eventName === 'result' &&
payload.profile &&
typeof payload.profile === 'object'
) {
latestProfile = payload.profile as Record<string, unknown>;
}
if (eventName === 'error') {
throw new Error(
typeof payload.message === 'string'
? payload.message
: '生成自定义世界失败',
);
}
}
}
}
if (!latestProfile) {
throw new Error('自定义世界生成未返回结果');
}
return latestProfile as unknown as CustomWorldProfile;
const aiClient = await loadLegacyAiModule();
return aiClient.generateCustomWorldProfile(input, options);
}
export async function generateCustomWorldSceneImage(
@@ -625,22 +439,6 @@ export async function generateCustomWorldLandmark(payload: {
return response.entity;
}
export async function createCustomWorldSession(payload: {
settingText: string;
creatorIntent?: Record<string, unknown> | null;
generationMode: 'fast' | 'full';
}) {
return requestJson<CustomWorldSessionSummary>(
`${RUNTIME_API_BASE}/custom-world/sessions`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload satisfies CreateCustomWorldSessionRequest),
},
'创建自定义世界会话失败',
);
}
export async function listCustomWorldWorks() {
const response = await requestJson<ListCustomWorldWorksResponse>(
`${RUNTIME_API_BASE}/custom-world/works`,
@@ -842,33 +640,6 @@ export async function getCustomWorldAgentCardDetail(
return response.card as CustomWorldDraftCardDetail;
}
export async function getCustomWorldSession(sessionId: string) {
return requestJson<CustomWorldSessionRecord>(
`${RUNTIME_API_BASE}/custom-world/sessions/${encodeURIComponent(sessionId)}`,
{
method: 'GET',
},
'读取自定义世界会话失败',
);
}
export async function answerCustomWorldSessionQuestion(
sessionId: string,
payload: { questionId: string; answer: string },
) {
return requestJson<CustomWorldSessionSummary>(
`${RUNTIME_API_BASE}/custom-world/sessions/${encodeURIComponent(sessionId)}/answers`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(
payload satisfies AnswerCustomWorldSessionQuestionRequest,
),
},
'提交自定义世界补充设定失败',
);
}
export async function streamCharacterPanelChatReply(
world: WorldType,
playerCharacter: Character,