This commit is contained in:
2026-04-26 20:50:58 +08:00
parent a3a9bfa194
commit 67161bd6d1
142 changed files with 3349 additions and 10674 deletions

View File

@@ -1,19 +1,37 @@
import type { CustomWorldSceneImageRequest, CustomWorldSceneImageResult } from '../aiTypes';
import {
generateCustomWorldCoverImage,
uploadCustomWorldCoverImage,
} from '../customWorldCoverAssetService';
import { requestJson } from '../apiClient';
import { ASSET_API_PATHS } from '../../editor/shared/editorApiClient';
import type {
CustomWorldLandmark,
CustomWorldNpc,
CustomWorldPlayableNpc,
CustomWorldProfile,
} from '../../types';
import type {
CustomWorldSceneImageRequest,
CustomWorldSceneImageResult,
} from '../aiTypes';
import { requestJson } from '../apiClient';
import {
generateCustomWorldCoverImage,
uploadCustomWorldCoverImage,
} from '../customWorldCoverAssetService';
import { requestRpgCreationPostJson } from './rpgCreationRequestHelpers';
const RPG_CREATION_ASSET_API_BASE = '/api/custom-world';
export type RpgCreationHistoryAssetKind = 'character_visual' | 'scene_image';
export type RpgCreationHistoryAsset = {
assetObjectId: string;
assetKind: RpgCreationHistoryAssetKind;
imageSrc: string;
ownerUserId?: string | null;
ownerLabel: string;
profileId?: string | null;
entityId?: string | null;
createdAt: string;
updatedAt: string;
};
export async function generateRpgWorldSceneImage(
payload: CustomWorldSceneImageRequest,
) {
@@ -28,6 +46,24 @@ export async function generateRpgWorldSceneImage(
);
}
export async function listRpgCreationHistoryAssets(payload: {
kind: RpgCreationHistoryAssetKind;
limit?: number;
}) {
const params = new URLSearchParams({ kind: payload.kind });
if (payload.limit) {
params.set('limit', String(payload.limit));
}
const response = await requestJson<{ assets: RpgCreationHistoryAsset[] }>(
`${ASSET_API_PATHS.assetHistory}?${params.toString()}`,
{ method: 'GET' },
'读取历史素材失败',
);
return response.assets;
}
export async function generateRpgWorldSceneNpc(payload: {
profile: CustomWorldProfile;
landmarkId: string;
@@ -101,6 +137,7 @@ export async function generateRpgWorldLandmark(payload: {
* 保留封面资产服务的既有边界,不把逻辑重新塞回 `aiService.ts`。
*/
export const rpgCreationAssetClient = {
listHistoryAssets: listRpgCreationHistoryAssets,
generateSceneImage: generateRpgWorldSceneImage,
generateSceneNpc: generateRpgWorldSceneNpc,
generatePlayableNpc: generateRpgWorldPlayableNpc,

View File

@@ -202,12 +202,13 @@ test('buildRpgCreationPreviewFromResultPreview normalizes server preview envelop
expect(profile?.settingText).toBe('被海雾吞没的旧航路群岛');
});
test('buildRpgCreationPreviewFromSession prefers server result preview', () => {
test('buildRpgCreationPreviewFromSession prefers agent draft profile', () => {
const profile = buildRpgCreationPreviewFromSession(sessionWithPreview);
expect(profile?.name).toBe('服务端结果预览');
expect(profile?.summary).toBe('结果页应该优先消费 session.resultPreview。');
expect(profile?.id).toBe('preview-profile-1');
expect(profile?.name).toBe('只作为 fallback 的本地草稿名');
expect(profile?.summary).toBe('fallback');
expect(profile?.id).toBe('draft-profile-1');
expect(profile?.playableNpcs[0]?.id).toBe('draft-playable-1');
});
test('buildRpgCreationPreviewFromSession does not require resultPreview', () => {

View File

@@ -15,14 +15,14 @@ export function buildCustomWorldProfileFromAgentSession(
session: CustomWorldAgentSessionSnapshot | null | undefined,
): CustomWorldProfile | null {
return (
buildCustomWorldProfileFromResultPreview(session?.resultPreview) ??
normalizeCustomWorldProfileRecord(session?.draftProfile ?? null)
normalizeCustomWorldProfileRecord(session?.draftProfile ?? null) ??
buildCustomWorldProfileFromResultPreview(session?.resultPreview)
);
}
/**
* 这是工作包 A 提供的新命名兼容层。
* 主入口保持命名稳定,优先消费服务端 resultPreview,缺失时回退到 draftProfile
* 主入口保持命名稳定,优先消费 Agent 草稿真相源,缺失时回退到 resultPreview
*/
export const rpgCreationPreviewAdapter = {
buildPreviewFromSession: buildCustomWorldProfileFromAgentSession,