1
This commit is contained in:
@@ -3,6 +3,7 @@ export {
|
||||
executeRpgCreationAction,
|
||||
getRpgCreationCardDetail,
|
||||
getRpgCreationOperation,
|
||||
getRpgCreationResultView,
|
||||
getRpgCreationSession,
|
||||
rpgCreationAgentClient,
|
||||
sendRpgCreationMessage,
|
||||
@@ -23,10 +24,7 @@ export type {
|
||||
GenerateCustomWorldProfileInput,
|
||||
GenerateCustomWorldProfileOptions,
|
||||
} from './rpgCreationGenerationClient';
|
||||
export {
|
||||
generateCustomWorldProfile as generateLegacyCustomWorldProfile,
|
||||
generateRpgWorldProfile,
|
||||
} from './rpgCreationGenerationClient';
|
||||
export { generateRpgWorldProfile } from './rpgCreationGenerationClient';
|
||||
export {
|
||||
deleteRpgWorldProfile,
|
||||
getRpgWorldGalleryDetail,
|
||||
@@ -39,6 +37,7 @@ export {
|
||||
} from './rpgCreationLibraryClient';
|
||||
export {
|
||||
buildRpgCreationPreviewFromResultPreview,
|
||||
buildRpgCreationPreviewFromResultView,
|
||||
buildRpgCreationPreviewFromSession,
|
||||
rpgCreationPreviewAdapter,
|
||||
} from './rpgCreationPreviewAdapter';
|
||||
|
||||
@@ -2,6 +2,7 @@ import type {
|
||||
CreateRpgAgentSessionRequest,
|
||||
CreateRpgAgentSessionResponse,
|
||||
GetRpgAgentCardDetailResponse,
|
||||
RpgCreationResultView,
|
||||
RpgAgentDraftCardDetail,
|
||||
RpgAgentOperationRecord,
|
||||
RpgAgentSessionSnapshot,
|
||||
@@ -46,6 +47,16 @@ export async function getRpgCreationSession(sessionId: string) {
|
||||
);
|
||||
}
|
||||
|
||||
export async function getRpgCreationResultView(sessionId: string) {
|
||||
return requestRpgCreationRuntimeJson<RpgCreationResultView>(
|
||||
`${RPG_AGENT_API_BASE}/${encodeURIComponent(sessionId)}/result-view`,
|
||||
{
|
||||
method: 'GET',
|
||||
},
|
||||
'读取世界结果页视图失败',
|
||||
);
|
||||
}
|
||||
|
||||
export async function sendRpgCreationMessage(
|
||||
sessionId: string,
|
||||
payload: SendRpgAgentMessageRequest,
|
||||
@@ -133,6 +144,7 @@ export async function getRpgCreationCardDetail(
|
||||
export const rpgCreationAgentClient = {
|
||||
createSession: createRpgCreationSession,
|
||||
getSession: getRpgCreationSession,
|
||||
getResultView: getRpgCreationResultView,
|
||||
sendMessage: sendRpgCreationMessage,
|
||||
streamMessage: streamRpgCreationMessage,
|
||||
executeAction: executeRpgCreationAction,
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/* @vitest-environment node */
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const { requestJsonMock } = vi.hoisted(() => ({
|
||||
requestJsonMock: vi.fn(),
|
||||
}));
|
||||
|
||||
import { generateRpgWorldProfile } from './rpgCreationGenerationClient';
|
||||
|
||||
vi.mock('../apiClient', () => ({
|
||||
requestJson: requestJsonMock,
|
||||
}));
|
||||
|
||||
vi.mock('../ai', () => ({
|
||||
generateCustomWorldProfile: vi.fn(() => {
|
||||
throw new Error('不应再调用前端 legacy AI 生成链');
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('rpgCreationGenerationClient node runtime', () => {
|
||||
beforeEach(() => {
|
||||
requestJsonMock.mockReset();
|
||||
requestJsonMock.mockResolvedValue({
|
||||
id: 'server-rs-profile-1',
|
||||
name: '服务端世界',
|
||||
subtitle: '副标题',
|
||||
summary: '概述',
|
||||
tone: '基调',
|
||||
playerGoal: '目标',
|
||||
settingText: '设定',
|
||||
});
|
||||
});
|
||||
|
||||
it('uses server-rs profile generation instead of importing legacy ai', async () => {
|
||||
const profile = await generateRpgWorldProfile('一个在 Node 测试中生成的世界');
|
||||
|
||||
expect(profile.id).toBe('server-rs-profile-1');
|
||||
expect(requestJsonMock).toHaveBeenCalledWith(
|
||||
'/api/runtime/custom-world/profile',
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
settingText: '一个在 Node 测试中生成的世界',
|
||||
}),
|
||||
}),
|
||||
'生成自定义世界失败',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -34,6 +34,9 @@ describe('rpgCreationGenerationClient', () => {
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
settingText: '一个被灵潮反复改写地形的边境世界',
|
||||
}),
|
||||
}),
|
||||
'生成自定义世界失败',
|
||||
);
|
||||
@@ -51,4 +54,26 @@ describe('rpgCreationGenerationClient', () => {
|
||||
|
||||
expect(requestJsonMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('passes abort signal to the backend request contract', async () => {
|
||||
const controller = new AbortController();
|
||||
|
||||
await generateRpgWorldProfile(
|
||||
{
|
||||
settingText: '一个由服务端生成的世界',
|
||||
generationMode: 'fast',
|
||||
},
|
||||
{
|
||||
signal: controller.signal,
|
||||
},
|
||||
);
|
||||
|
||||
expect(requestJsonMock).toHaveBeenCalledWith(
|
||||
'/api/runtime/custom-world/profile',
|
||||
expect.objectContaining({
|
||||
signal: controller.signal,
|
||||
}),
|
||||
'生成自定义世界失败',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,18 +6,6 @@ import type {
|
||||
import type { CustomWorldProfile } from '../../types';
|
||||
import { requestJson } from '../apiClient';
|
||||
|
||||
type LegacyAiModule = typeof import('../ai');
|
||||
|
||||
let legacyAiModulePromise: Promise<LegacyAiModule> | null = null;
|
||||
|
||||
async function loadLegacyAiModule() {
|
||||
if (!legacyAiModulePromise) {
|
||||
legacyAiModulePromise = import('../ai');
|
||||
}
|
||||
|
||||
return legacyAiModulePromise;
|
||||
}
|
||||
|
||||
export async function generateRpgWorldProfile(
|
||||
input: GenerateCustomWorldProfileInput | string,
|
||||
options: GenerateCustomWorldProfileOptions = {},
|
||||
@@ -29,11 +17,6 @@ export async function generateRpgWorldProfile(
|
||||
}
|
||||
: input;
|
||||
|
||||
if (typeof window === 'undefined') {
|
||||
const aiClient = await loadLegacyAiModule();
|
||||
return aiClient.generateCustomWorldProfile(normalizedInput, options);
|
||||
}
|
||||
|
||||
if (options.signal?.aborted) {
|
||||
throw options.signal.reason instanceof Error
|
||||
? options.signal.reason
|
||||
@@ -45,6 +28,7 @@ export async function generateRpgWorldProfile(
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
signal: options.signal,
|
||||
body: JSON.stringify(normalizedInput),
|
||||
},
|
||||
'生成自定义世界失败',
|
||||
|
||||
@@ -3,6 +3,7 @@ import { expect, test } from 'vitest';
|
||||
import type { CustomWorldAgentSessionSnapshot } from '../../../packages/shared/src/contracts/customWorldAgent';
|
||||
import {
|
||||
buildRpgCreationPreviewFromResultPreview,
|
||||
buildRpgCreationPreviewFromResultView,
|
||||
buildRpgCreationPreviewFromSession,
|
||||
} from './rpgCreationPreviewAdapter';
|
||||
|
||||
@@ -211,7 +212,7 @@ test('buildRpgCreationPreviewFromSession prefers server result preview', () => {
|
||||
expect(profile?.playableNpcs).toEqual([]);
|
||||
});
|
||||
|
||||
test('buildRpgCreationPreviewFromSession falls back to draft legacy result profile', () => {
|
||||
test('buildRpgCreationPreviewFromSession no longer reads draft legacy result profile', () => {
|
||||
const profile = buildRpgCreationPreviewFromSession({
|
||||
...sessionWithPreview,
|
||||
resultPreview: null,
|
||||
@@ -226,11 +227,36 @@ test('buildRpgCreationPreviewFromSession falls back to draft legacy result profi
|
||||
},
|
||||
});
|
||||
|
||||
expect(profile?.name).toBe('草稿内嵌结果页');
|
||||
expect(profile?.summary).toBe(
|
||||
'resultPreview 缺失时继续使用 draft 内嵌的结果页快照。',
|
||||
);
|
||||
expect(profile?.id).toBe('legacy-result-profile-1');
|
||||
expect(profile).toBeNull();
|
||||
});
|
||||
|
||||
test('buildRpgCreationPreviewFromResultView consumes backend-selected profile', () => {
|
||||
const profile = buildRpgCreationPreviewFromResultView({
|
||||
session: {
|
||||
...sessionWithPreview,
|
||||
resultPreview: null,
|
||||
},
|
||||
profile: {
|
||||
...sessionWithPreview.resultPreview!.preview,
|
||||
id: 'backend-selected-profile-1',
|
||||
name: '后端结果页真相',
|
||||
summary: 'legacy 兼容只允许在后端 result-view 内完成。',
|
||||
},
|
||||
profileSource: 'draft_profile',
|
||||
targetStage: 'custom-world-result',
|
||||
generationViewSource: null,
|
||||
resultViewSource: 'agent-draft',
|
||||
canAutosaveLibrary: true,
|
||||
canSyncResultProfile: true,
|
||||
publishReady: false,
|
||||
canEnterWorld: false,
|
||||
blockerCount: 0,
|
||||
recoveryAction: 'open_result',
|
||||
});
|
||||
|
||||
expect(profile?.name).toBe('后端结果页真相');
|
||||
expect(profile?.summary).toBe('legacy 兼容只允许在后端 result-view 内完成。');
|
||||
expect(profile?.id).toBe('backend-selected-profile-1');
|
||||
});
|
||||
|
||||
test('buildRpgCreationPreviewFromSession does not treat draftProfile as runtime profile', () => {
|
||||
|
||||
@@ -1,20 +1,8 @@
|
||||
import type { CustomWorldAgentSessionSnapshot } from '../../../packages/shared/src/contracts/customWorldAgent';
|
||||
import type { RpgCreationResultView } from '../../../packages/shared/src/contracts/rpgCreationResultView';
|
||||
import { normalizeCustomWorldProfileRecord } from '../../data/customWorldLibrary';
|
||||
import type { CustomWorldProfile } from '../../types';
|
||||
|
||||
function buildCustomWorldProfileFromDraftLegacyResult(
|
||||
draftProfile: CustomWorldAgentSessionSnapshot['draftProfile'],
|
||||
): CustomWorldProfile | null {
|
||||
if (!draftProfile || typeof draftProfile !== 'object') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return normalizeCustomWorldProfileRecord(
|
||||
(draftProfile as { legacyResultProfile?: unknown }).legacyResultProfile ??
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
export function buildCustomWorldProfileFromResultPreview(
|
||||
resultPreview:
|
||||
| CustomWorldAgentSessionSnapshot['resultPreview']
|
||||
@@ -27,10 +15,13 @@ export function buildCustomWorldProfileFromResultPreview(
|
||||
export function buildCustomWorldProfileFromAgentSession(
|
||||
session: CustomWorldAgentSessionSnapshot | null | undefined,
|
||||
): CustomWorldProfile | null {
|
||||
return (
|
||||
buildCustomWorldProfileFromResultPreview(session?.resultPreview) ??
|
||||
buildCustomWorldProfileFromDraftLegacyResult(session?.draftProfile ?? null)
|
||||
);
|
||||
return buildCustomWorldProfileFromResultPreview(session?.resultPreview);
|
||||
}
|
||||
|
||||
export function buildCustomWorldProfileFromResultView(
|
||||
view: RpgCreationResultView | null | undefined,
|
||||
): CustomWorldProfile | null {
|
||||
return normalizeCustomWorldProfileRecord(view?.profile ?? null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,9 +31,11 @@ export function buildCustomWorldProfileFromAgentSession(
|
||||
export const rpgCreationPreviewAdapter = {
|
||||
buildPreviewFromSession: buildCustomWorldProfileFromAgentSession,
|
||||
buildPreviewFromResultPreview: buildCustomWorldProfileFromResultPreview,
|
||||
buildPreviewFromResultView: buildCustomWorldProfileFromResultView,
|
||||
};
|
||||
|
||||
export {
|
||||
buildCustomWorldProfileFromResultPreview as buildRpgCreationPreviewFromResultPreview,
|
||||
buildCustomWorldProfileFromAgentSession as buildRpgCreationPreviewFromSession,
|
||||
buildCustomWorldProfileFromResultView as buildRpgCreationPreviewFromResultView,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user