@@ -899,13 +899,21 @@ describe('ai orchestration fallbacks', () => {
|
||||
ok: true,
|
||||
text: async () =>
|
||||
JSON.stringify({
|
||||
imageSrc: '/generated-custom-world-scenes/world/landmark/scene.png',
|
||||
assetId: 'custom-scene-1',
|
||||
model: 'wan2.7-image',
|
||||
size: '1280*720',
|
||||
taskId: 'task-123',
|
||||
prompt: '系统整理后的提示词',
|
||||
actualPrompt: '扩写后的提示词',
|
||||
ok: true,
|
||||
data: {
|
||||
ok: true,
|
||||
imageSrc: '/generated-custom-world-scenes/world/landmark/scene.png',
|
||||
assetId: 'custom-scene-1',
|
||||
model: 'wan2.7-image',
|
||||
size: '1280*720',
|
||||
taskId: 'task-123',
|
||||
prompt: '系统整理后的提示词',
|
||||
actualPrompt: '扩写后的提示词',
|
||||
},
|
||||
error: null,
|
||||
meta: {
|
||||
apiVersion: '2026-04-08',
|
||||
},
|
||||
}),
|
||||
} as Response);
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import type {
|
||||
CustomWorldGenerationStep,
|
||||
GenerateCustomWorldProfileInput,
|
||||
GenerateCustomWorldProfileOptions,
|
||||
} from '../../packages/shared/src/contracts/runtime';
|
||||
import { unwrapApiResponse } from '../../packages/shared/src/http';
|
||||
import { createSceneHostileNpcsFromEncounters } from '../data/hostileNpcs';
|
||||
import {
|
||||
buildEncounterFromSceneNpc,
|
||||
@@ -26,12 +32,6 @@ import {
|
||||
WorldStoryGraph,
|
||||
WorldType,
|
||||
} from '../types';
|
||||
import type {
|
||||
CustomWorldGenerationStep,
|
||||
CustomWorldGenerationProgress,
|
||||
GenerateCustomWorldProfileInput,
|
||||
GenerateCustomWorldProfileOptions,
|
||||
} from '../../packages/shared/src/contracts/runtime';
|
||||
import {
|
||||
buildOfflineCharacterPanelChatReply as buildOfflineCharacterPanelChatReplyFromFallback,
|
||||
buildOfflineCharacterPanelChatSuggestions as buildOfflineCharacterPanelChatSuggestionsFromFallback,
|
||||
@@ -136,7 +136,6 @@ export type {
|
||||
GenerateCustomWorldProfileInput,
|
||||
GenerateCustomWorldProfileOptions,
|
||||
} from '../../packages/shared/src/contracts/runtime';
|
||||
|
||||
export type {
|
||||
StoryGenerationContext,
|
||||
StoryRequestOptions,
|
||||
@@ -2018,8 +2017,8 @@ export async function generateCustomWorldSceneImage({
|
||||
);
|
||||
}
|
||||
|
||||
const data = JSON.parse(
|
||||
responseText,
|
||||
const data = unwrapApiResponse(
|
||||
JSON.parse(responseText) as Partial<CustomWorldSceneImageResult>,
|
||||
) as Partial<CustomWorldSceneImageResult>;
|
||||
if (
|
||||
!data.imageSrc ||
|
||||
|
||||
@@ -30,6 +30,9 @@ import { parseApiErrorMessage } from '../../packages/shared/src/http';
|
||||
import type {
|
||||
AIResponse,
|
||||
Character,
|
||||
CustomWorldLandmark,
|
||||
CustomWorldNpc,
|
||||
CustomWorldPlayableNpc,
|
||||
CharacterChatTurn,
|
||||
CustomWorldProfile,
|
||||
Encounter,
|
||||
@@ -49,6 +52,7 @@ import { type CharacterChatTargetStatus } from './characterChatPrompt';
|
||||
import { parseLineListContent } from './llmParsers';
|
||||
|
||||
const RUNTIME_API_BASE = '/api/runtime';
|
||||
const CUSTOM_WORLD_API_BASE = '/api';
|
||||
|
||||
type LegacyAiModule = typeof import('./ai');
|
||||
|
||||
@@ -490,6 +494,74 @@ export async function generateCustomWorldSceneImage(
|
||||
return aiClient.generateCustomWorldSceneImage(...args);
|
||||
}
|
||||
|
||||
export async function generateCustomWorldSceneNpc(payload: {
|
||||
profile: CustomWorldProfile;
|
||||
landmarkId: string;
|
||||
}) {
|
||||
const response = await requestPostJson<{ npc: CustomWorldNpc }>(
|
||||
`${CUSTOM_WORLD_API_BASE}/custom-world/scene-npc`,
|
||||
payload,
|
||||
'生成场景 NPC 失败',
|
||||
);
|
||||
|
||||
return response.npc;
|
||||
}
|
||||
|
||||
async function requestCustomWorldEntity<T>(
|
||||
payload: {
|
||||
profile: CustomWorldProfile;
|
||||
kind: 'playable' | 'story' | 'landmark';
|
||||
},
|
||||
fallbackMessage: string,
|
||||
) {
|
||||
return requestPostJson<{
|
||||
kind: 'playable' | 'story' | 'landmark';
|
||||
entity: T;
|
||||
}>(`${CUSTOM_WORLD_API_BASE}/custom-world/entity`, payload, fallbackMessage);
|
||||
}
|
||||
|
||||
export async function generateCustomWorldPlayableNpc(payload: {
|
||||
profile: CustomWorldProfile;
|
||||
}) {
|
||||
const response = await requestCustomWorldEntity<CustomWorldPlayableNpc>(
|
||||
{
|
||||
...payload,
|
||||
kind: 'playable',
|
||||
},
|
||||
'生成可扮演角色失败',
|
||||
);
|
||||
|
||||
return response.entity;
|
||||
}
|
||||
|
||||
export async function generateCustomWorldStoryNpc(payload: {
|
||||
profile: CustomWorldProfile;
|
||||
}) {
|
||||
const response = await requestCustomWorldEntity<CustomWorldNpc>(
|
||||
{
|
||||
...payload,
|
||||
kind: 'story',
|
||||
},
|
||||
'生成场景角色失败',
|
||||
);
|
||||
|
||||
return response.entity;
|
||||
}
|
||||
|
||||
export async function generateCustomWorldLandmark(payload: {
|
||||
profile: CustomWorldProfile;
|
||||
}) {
|
||||
const response = await requestCustomWorldEntity<CustomWorldLandmark>(
|
||||
{
|
||||
...payload,
|
||||
kind: 'landmark',
|
||||
},
|
||||
'生成场景失败',
|
||||
);
|
||||
|
||||
return response.entity;
|
||||
}
|
||||
|
||||
export async function createCustomWorldSession(payload: {
|
||||
settingText: string;
|
||||
creatorIntent?: Record<string, unknown> | null;
|
||||
|
||||
@@ -2354,7 +2354,7 @@ export function buildCustomWorldSceneImagePrompt(
|
||||
'下半部分的内容必须是明确可站立的地面本体,例如道路、石板、平台、广场、甲板、沙地或草地,要有连续、稳定、可落脚的站位逻辑,不能只是装饰性前景、坑洞、障碍堆、栏杆带或不可通行的景物。',
|
||||
'下半部分地面近景要保持相对简洁、低细节、轮廓清楚、便于角色站立,不要堆满道具、植被、碎石、栏杆或复杂装饰。',
|
||||
options.hasReferenceImage
|
||||
? '已提供一张自定义参考图,可适度参考其构图、镜头或氛围,但仍以本次场景需求为准,不要生硬照搬。'
|
||||
? '已提供一张自定义参考图,请沿用其构图、镜头或氛围线索,同时继续满足本次场景需求。'
|
||||
: '',
|
||||
`世界:${worldName}${worldSubtitle ? `,${worldSubtitle}` : ''}。`,
|
||||
worldSetting ? `玩家设定:${worldSetting}。` : '',
|
||||
|
||||
@@ -116,9 +116,9 @@ test('marks all legacy progress steps complete when draft foundation finishes',
|
||||
test('builds readable draft setting text from creator intent first', () => {
|
||||
const settingText = buildAgentDraftFoundationSettingText(baseSession);
|
||||
|
||||
expect(settingText).toContain('世界核心命题');
|
||||
expect(settingText).toContain('玩家身份');
|
||||
expect(settingText).toContain('标志性要素');
|
||||
expect(settingText).toContain('世界一句话');
|
||||
expect(settingText).toContain('玩家开局');
|
||||
expect(settingText).toContain('标志元素');
|
||||
});
|
||||
|
||||
test('falls back to latest user message when creator intent is unavailable', () => {
|
||||
|
||||
@@ -7,8 +7,7 @@ import type {
|
||||
CustomWorldGenerationStep,
|
||||
} from '../../packages/shared/src/contracts/runtime';
|
||||
import {
|
||||
buildCustomWorldCreatorIntentDisplayText,
|
||||
buildCustomWorldCreatorIntentGenerationText,
|
||||
buildCustomWorldCreatorIntentFoundationText,
|
||||
normalizeCustomWorldCreatorIntent,
|
||||
} from './customWorldCreatorIntent';
|
||||
|
||||
@@ -177,17 +176,11 @@ export function buildAgentDraftFoundationSettingText(
|
||||
);
|
||||
|
||||
if (creatorIntent) {
|
||||
const generationText =
|
||||
buildCustomWorldCreatorIntentGenerationText(creatorIntent).trim();
|
||||
const displayText =
|
||||
buildCustomWorldCreatorIntentDisplayText(creatorIntent).trim();
|
||||
const foundationText =
|
||||
buildCustomWorldCreatorIntentFoundationText(creatorIntent).trim();
|
||||
|
||||
if (generationText) {
|
||||
return generationText;
|
||||
}
|
||||
|
||||
if (displayText) {
|
||||
return displayText;
|
||||
if (foundationText) {
|
||||
return foundationText;
|
||||
}
|
||||
|
||||
if (creatorIntent.rawSettingText.trim()) {
|
||||
|
||||
@@ -2,8 +2,9 @@ import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
buildCustomWorldAnchorPackFromIntent,
|
||||
buildPendingClarifications,
|
||||
buildCustomWorldCreatorIntentDisplayText,
|
||||
buildCustomWorldCreatorIntentFoundationText,
|
||||
buildPendingClarifications,
|
||||
createEmptyCustomWorldCreatorIntent,
|
||||
evaluateCustomWorldCreatorIntentReadiness,
|
||||
mergeCustomWorldCreatorIntent,
|
||||
@@ -42,6 +43,41 @@ describe('customWorldCreatorIntent', () => {
|
||||
expect(summary).toContain('关键角色:沈砺 / 灰炬向导');
|
||||
});
|
||||
|
||||
it('builds six-anchor foundation text from structured creator intent', () => {
|
||||
const intent = {
|
||||
...createEmptyCustomWorldCreatorIntent('card'),
|
||||
worldHook: '一个会被灵潮反复改写地形的边境世界。',
|
||||
themeKeywords: ['边境', '灵潮'],
|
||||
toneDirectives: ['紧张', '潮湿'],
|
||||
playerPremise: '玩家是带着旧名单回来的前巡夜人。',
|
||||
openingSituation: '返乡第一夜,封锁线外出现了本不该存在的灯火。',
|
||||
coreConflicts: ['旧案名单再次出现'],
|
||||
keyCharacters: [
|
||||
{
|
||||
id: 'character-1',
|
||||
name: '沈砺',
|
||||
role: '灰炬向导',
|
||||
publicMask: '看起来只是熟路的带路人',
|
||||
hiddenHook: '他一直在追查撤离线失控真相',
|
||||
relationToPlayer: '会先怀疑玩家身份',
|
||||
notes: '',
|
||||
locked: true,
|
||||
},
|
||||
],
|
||||
iconicElements: ['会逆向蔓延的潮雾'],
|
||||
};
|
||||
|
||||
const foundationText = buildCustomWorldCreatorIntentFoundationText(intent);
|
||||
|
||||
expect(foundationText).toContain(
|
||||
'世界一句话:一个会被灵潮反复改写地形的边境世界。',
|
||||
);
|
||||
expect(foundationText).toContain('玩家开局:玩家是带着旧名单回来的前巡夜人。');
|
||||
expect(foundationText).toContain('主题气质:边境、灵潮 / 紧张、潮湿');
|
||||
expect(foundationText).toContain('关键关系:沈砺 · 灰炬向导');
|
||||
expect(foundationText).toContain('标志元素:会逆向蔓延的潮雾');
|
||||
});
|
||||
|
||||
it('builds anchor pack from creator intent and keeps locked ids', () => {
|
||||
const intent = {
|
||||
...createEmptyCustomWorldCreatorIntent('card'),
|
||||
|
||||
@@ -710,6 +710,48 @@ function buildAnchorLine(label: string, content: string) {
|
||||
return content ? `${label}:${content}` : '';
|
||||
}
|
||||
|
||||
export function buildCustomWorldCreatorIntentFoundationText(
|
||||
intent: CustomWorldCreatorIntent | null | undefined,
|
||||
) {
|
||||
if (!hasMeaningfulCustomWorldCreatorIntent(intent)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const relationshipSeed = intent?.keyCharacters[0];
|
||||
const relationshipText = relationshipSeed
|
||||
? [
|
||||
relationshipSeed.name,
|
||||
relationshipSeed.role,
|
||||
relationshipSeed.relationToPlayer
|
||||
? `与玩家 ${relationshipSeed.relationToPlayer}`
|
||||
: '',
|
||||
relationshipSeed.hiddenHook ? `暗线 ${relationshipSeed.hiddenHook}` : '',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' · ')
|
||||
: '';
|
||||
const playerOpeningText = [intent?.playerPremise || '', intent?.openingSituation || '']
|
||||
.filter(Boolean)
|
||||
.join(';');
|
||||
const themeToneText = [
|
||||
intent?.themeKeywords.join('、') || '',
|
||||
intent?.toneDirectives.join('、') || '',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' / ');
|
||||
|
||||
return [
|
||||
buildAnchorLine('世界一句话', intent?.worldHook || ''),
|
||||
buildAnchorLine('玩家开局', playerOpeningText),
|
||||
buildAnchorLine('主题气质', themeToneText),
|
||||
buildAnchorLine('核心冲突', intent?.coreConflicts.join(';') || ''),
|
||||
buildAnchorLine('关键关系', relationshipText),
|
||||
buildAnchorLine('标志元素', intent?.iconicElements.join('、') || ''),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
export function buildCustomWorldCreatorIntentDisplayText(
|
||||
intent: CustomWorldCreatorIntent | null | undefined,
|
||||
) {
|
||||
|
||||
@@ -1,23 +1,19 @@
|
||||
import type { CustomWorldGalleryCard } from '../../packages/shared/src/contracts/runtime';
|
||||
import type {
|
||||
PlatformBrowseHistoryEntry,
|
||||
PlatformBrowseHistoryWriteEntry,
|
||||
} from '../../packages/shared/src/contracts/runtime';
|
||||
import type { AuthUser } from './authService';
|
||||
|
||||
export type PlatformBrowseHistoryEntry = {
|
||||
ownerUserId: string;
|
||||
profileId: string;
|
||||
worldName: string;
|
||||
subtitle: string;
|
||||
summaryText: string;
|
||||
coverImageSrc: string | null;
|
||||
themeMode: CustomWorldGalleryCard['themeMode'];
|
||||
authorDisplayName: string;
|
||||
visitedAt: string;
|
||||
};
|
||||
export type { PlatformBrowseHistoryEntry, PlatformBrowseHistoryWriteEntry };
|
||||
|
||||
const HISTORY_STORAGE_KEY_PREFIX = 'genarrative.platform.browse-history.v1';
|
||||
const HISTORY_SYNC_KEY_PREFIX = 'genarrative.platform.browse-history.synced.v1';
|
||||
const MAX_HISTORY_ENTRIES = 20;
|
||||
|
||||
function canUseLocalStorage() {
|
||||
return typeof window !== 'undefined' && typeof window.localStorage !== 'undefined';
|
||||
return (
|
||||
typeof window !== 'undefined' && typeof window.localStorage !== 'undefined'
|
||||
);
|
||||
}
|
||||
|
||||
function buildHistoryStorageKey(user: AuthUser | null | undefined) {
|
||||
@@ -25,6 +21,11 @@ function buildHistoryStorageKey(user: AuthUser | null | undefined) {
|
||||
return `${HISTORY_STORAGE_KEY_PREFIX}:${accountId}`;
|
||||
}
|
||||
|
||||
function buildHistorySyncKey(user: AuthUser | null | undefined) {
|
||||
const accountId = user?.id?.trim() || user?.username?.trim() || 'guest';
|
||||
return `${HISTORY_SYNC_KEY_PREFIX}:${accountId}`;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
}
|
||||
@@ -33,7 +34,9 @@ function readString(value: unknown) {
|
||||
return typeof value === 'string' ? value.trim() : '';
|
||||
}
|
||||
|
||||
function normalizeHistoryEntry(value: unknown): PlatformBrowseHistoryEntry | null {
|
||||
function normalizeHistoryEntry(
|
||||
value: unknown,
|
||||
): PlatformBrowseHistoryEntry | null {
|
||||
if (!isRecord(value)) {
|
||||
return null;
|
||||
}
|
||||
@@ -42,12 +45,11 @@ function normalizeHistoryEntry(value: unknown): PlatformBrowseHistoryEntry | nul
|
||||
const profileId = readString(value.profileId);
|
||||
const worldName = readString(value.worldName);
|
||||
const visitedAt = readString(value.visitedAt);
|
||||
|
||||
if (!ownerUserId || !profileId || !worldName || !visitedAt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const themeMode = readString(value.themeMode) as PlatformBrowseHistoryEntry['themeMode'];
|
||||
|
||||
return {
|
||||
ownerUserId,
|
||||
profileId,
|
||||
@@ -55,7 +57,10 @@ function normalizeHistoryEntry(value: unknown): PlatformBrowseHistoryEntry | nul
|
||||
subtitle: readString(value.subtitle),
|
||||
summaryText: readString(value.summaryText),
|
||||
coverImageSrc: readString(value.coverImageSrc) || null,
|
||||
themeMode: themeMode || 'mythic',
|
||||
themeMode:
|
||||
(readString(
|
||||
value.themeMode,
|
||||
) as PlatformBrowseHistoryEntry['themeMode']) || 'mythic',
|
||||
authorDisplayName: readString(value.authorDisplayName) || '玩家',
|
||||
visitedAt,
|
||||
};
|
||||
@@ -97,19 +102,20 @@ export function readPlatformBrowseHistory(user: AuthUser | null | undefined) {
|
||||
|
||||
export function writePlatformBrowseHistory(
|
||||
user: AuthUser | null | undefined,
|
||||
entry: Omit<PlatformBrowseHistoryEntry, 'visitedAt'> & {
|
||||
visitedAt?: string;
|
||||
},
|
||||
entry: PlatformBrowseHistoryWriteEntry,
|
||||
) {
|
||||
if (!canUseLocalStorage()) {
|
||||
return [] as PlatformBrowseHistoryEntry[];
|
||||
}
|
||||
|
||||
const nextEntry: PlatformBrowseHistoryEntry = {
|
||||
...entry,
|
||||
ownerUserId: entry.ownerUserId.trim(),
|
||||
profileId: entry.profileId.trim(),
|
||||
worldName: entry.worldName.trim(),
|
||||
subtitle: entry.subtitle?.trim() || '',
|
||||
summaryText: entry.summaryText?.trim() || '',
|
||||
coverImageSrc: entry.coverImageSrc?.trim() || null,
|
||||
themeMode: entry.themeMode || 'mythic',
|
||||
authorDisplayName: entry.authorDisplayName?.trim() || '玩家',
|
||||
visitedAt: entry.visitedAt?.trim() || new Date().toISOString(),
|
||||
};
|
||||
@@ -129,5 +135,38 @@ export function writePlatformBrowseHistory(
|
||||
buildHistoryStorageKey(user),
|
||||
JSON.stringify(nextEntries),
|
||||
);
|
||||
|
||||
return nextEntries;
|
||||
}
|
||||
|
||||
export function clearPlatformBrowseHistory(user: AuthUser | null | undefined) {
|
||||
if (!canUseLocalStorage()) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.localStorage.removeItem(buildHistoryStorageKey(user));
|
||||
window.localStorage.removeItem(buildHistorySyncKey(user));
|
||||
}
|
||||
|
||||
export function hasPendingPlatformBrowseHistoryMigration(
|
||||
user: AuthUser | null | undefined,
|
||||
) {
|
||||
if (!canUseLocalStorage()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
readPlatformBrowseHistory(user).length > 0 &&
|
||||
window.localStorage.getItem(buildHistorySyncKey(user)) !== '1'
|
||||
);
|
||||
}
|
||||
|
||||
export function markPlatformBrowseHistoryMigrated(
|
||||
user: AuthUser | null | undefined,
|
||||
) {
|
||||
if (!canUseLocalStorage()) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.localStorage.setItem(buildHistorySyncKey(user), '1');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import type {
|
||||
ListCustomWorldWorksResponse,
|
||||
} from '../../packages/shared/src/contracts/customWorldAgent';
|
||||
import type { ListCustomWorldWorksResponse } from '../../packages/shared/src/contracts/customWorldAgent';
|
||||
import type {
|
||||
BasicOkResult,
|
||||
CustomWorldGalleryDetailResponse,
|
||||
@@ -8,15 +6,20 @@ import type {
|
||||
CustomWorldLibraryEntry,
|
||||
CustomWorldLibraryMutationResponse,
|
||||
CustomWorldLibraryResponse,
|
||||
PlatformBrowseHistoryBatchSyncRequest,
|
||||
PlatformBrowseHistoryEntry,
|
||||
PlatformBrowseHistoryResponse,
|
||||
PlatformBrowseHistoryWriteEntry,
|
||||
ProfileDashboardSummary,
|
||||
ProfilePlayStatsResponse,
|
||||
ProfileWalletLedgerResponse,
|
||||
RuntimeSettings,
|
||||
} from '../../packages/shared/src/contracts/runtime';
|
||||
import type {
|
||||
SavedGameSnapshotInput,
|
||||
} from '../persistence/gameSaveStorage';
|
||||
import type { SavedGameSnapshotInput } from '../persistence/gameSaveStorage';
|
||||
import { rehydrateSavedSnapshot } from '../persistence/runtimeSnapshot';
|
||||
import type { HydratedSavedGameSnapshot } from '../persistence/runtimeSnapshotTypes';
|
||||
import type { CustomWorldProfile } from '../types';
|
||||
import { type ApiRetryOptions,requestJson } from './apiClient';
|
||||
import { type ApiRetryOptions, requestJson } from './apiClient';
|
||||
|
||||
const RUNTIME_API_BASE = '/api/runtime';
|
||||
const RUNTIME_READ_RETRY: ApiRetryOptions = {
|
||||
@@ -58,6 +61,28 @@ function requestRuntimeJson<T>(
|
||||
);
|
||||
}
|
||||
|
||||
function requestProfileJson<T>(
|
||||
path: string,
|
||||
init: RequestInit,
|
||||
fallbackMessage: string,
|
||||
options: RuntimeRequestOptions = {},
|
||||
) {
|
||||
const method = (init.method ?? 'GET').toUpperCase();
|
||||
const retry =
|
||||
options.retry ??
|
||||
(method === 'GET' ? RUNTIME_READ_RETRY : RUNTIME_WRITE_RETRY);
|
||||
|
||||
return requestJson<T>(
|
||||
`/api/profile${path}`,
|
||||
{
|
||||
...init,
|
||||
signal: options.signal,
|
||||
},
|
||||
fallbackMessage,
|
||||
{ retry },
|
||||
);
|
||||
}
|
||||
|
||||
export async function getSaveSnapshot(options: RuntimeRequestOptions = {}) {
|
||||
const snapshot = await requestRuntimeJson<HydratedSavedGameSnapshot | null>(
|
||||
'/save/snapshot',
|
||||
@@ -105,6 +130,35 @@ export async function getSettings(options: RuntimeRequestOptions = {}) {
|
||||
);
|
||||
}
|
||||
|
||||
export async function getProfileDashboard(options: RuntimeRequestOptions = {}) {
|
||||
return requestRuntimeJson<ProfileDashboardSummary>(
|
||||
'/profile/dashboard',
|
||||
{ method: 'GET' },
|
||||
'读取个人看板失败',
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
export async function getProfileWalletLedger(
|
||||
options: RuntimeRequestOptions = {},
|
||||
) {
|
||||
return requestRuntimeJson<ProfileWalletLedgerResponse>(
|
||||
'/profile/wallet-ledger',
|
||||
{ method: 'GET' },
|
||||
'读取资产流水失败',
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
export async function getProfilePlayStats(options: RuntimeRequestOptions = {}) {
|
||||
return requestRuntimeJson<ProfilePlayStatsResponse>(
|
||||
'/profile/play-stats',
|
||||
{ method: 'GET' },
|
||||
'读取游玩统计失败',
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
export async function putSettings(
|
||||
settings: RuntimeSettings,
|
||||
options: RuntimeRequestOptions = {},
|
||||
@@ -121,8 +175,12 @@ export async function putSettings(
|
||||
);
|
||||
}
|
||||
|
||||
export async function listCustomWorldLibrary(options: RuntimeRequestOptions = {}) {
|
||||
const response = await requestRuntimeJson<CustomWorldLibraryResponse<CustomWorldProfile>>(
|
||||
export async function listCustomWorldLibrary(
|
||||
options: RuntimeRequestOptions = {},
|
||||
) {
|
||||
const response = await requestRuntimeJson<
|
||||
CustomWorldLibraryResponse<CustomWorldProfile>
|
||||
>(
|
||||
'/custom-world-library',
|
||||
{ method: 'GET' },
|
||||
'读取自定义世界库失败',
|
||||
@@ -132,7 +190,9 @@ export async function listCustomWorldLibrary(options: RuntimeRequestOptions = {}
|
||||
return Array.isArray(response?.entries) ? response.entries : [];
|
||||
}
|
||||
|
||||
export async function listCustomWorldWorks(options: RuntimeRequestOptions = {}) {
|
||||
export async function listCustomWorldWorks(
|
||||
options: RuntimeRequestOptions = {},
|
||||
) {
|
||||
const response = await requestRuntimeJson<ListCustomWorldWorksResponse>(
|
||||
'/custom-world/works',
|
||||
{ method: 'GET' },
|
||||
@@ -147,7 +207,9 @@ export async function upsertCustomWorldProfile(
|
||||
profile: CustomWorldProfile,
|
||||
options: RuntimeRequestOptions = {},
|
||||
) {
|
||||
const response = await requestRuntimeJson<CustomWorldLibraryMutationResponse<CustomWorldProfile>>(
|
||||
const response = await requestRuntimeJson<
|
||||
CustomWorldLibraryMutationResponse<CustomWorldProfile>
|
||||
>(
|
||||
`/custom-world-library/${encodeURIComponent(profile.id)}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
@@ -170,7 +232,9 @@ export async function deleteCustomWorldProfile(
|
||||
profileId: string,
|
||||
options: RuntimeRequestOptions = {},
|
||||
) {
|
||||
const response = await requestRuntimeJson<CustomWorldLibraryResponse<CustomWorldProfile>>(
|
||||
const response = await requestRuntimeJson<
|
||||
CustomWorldLibraryResponse<CustomWorldProfile>
|
||||
>(
|
||||
`/custom-world-library/${encodeURIComponent(profileId)}`,
|
||||
{ method: 'DELETE' },
|
||||
'删除自定义世界失败',
|
||||
@@ -184,7 +248,9 @@ export async function publishCustomWorldProfile(
|
||||
profileId: string,
|
||||
options: RuntimeRequestOptions = {},
|
||||
) {
|
||||
const response = await requestRuntimeJson<CustomWorldLibraryMutationResponse<CustomWorldProfile>>(
|
||||
const response = await requestRuntimeJson<
|
||||
CustomWorldLibraryMutationResponse<CustomWorldProfile>
|
||||
>(
|
||||
`/custom-world-library/${encodeURIComponent(profileId)}/publish`,
|
||||
{ method: 'POST' },
|
||||
'发布自定义世界失败',
|
||||
@@ -201,7 +267,9 @@ export async function unpublishCustomWorldProfile(
|
||||
profileId: string,
|
||||
options: RuntimeRequestOptions = {},
|
||||
) {
|
||||
const response = await requestRuntimeJson<CustomWorldLibraryMutationResponse<CustomWorldProfile>>(
|
||||
const response = await requestRuntimeJson<
|
||||
CustomWorldLibraryMutationResponse<CustomWorldProfile>
|
||||
>(
|
||||
`/custom-world-library/${encodeURIComponent(profileId)}/unpublish`,
|
||||
{ method: 'POST' },
|
||||
'下架自定义世界失败',
|
||||
@@ -214,7 +282,9 @@ export async function unpublishCustomWorldProfile(
|
||||
};
|
||||
}
|
||||
|
||||
export async function listCustomWorldGallery(options: RuntimeRequestOptions = {}) {
|
||||
export async function listCustomWorldGallery(
|
||||
options: RuntimeRequestOptions = {},
|
||||
) {
|
||||
const response = await requestRuntimeJson<CustomWorldGalleryResponse>(
|
||||
'/custom-world-gallery',
|
||||
{ method: 'GET' },
|
||||
@@ -230,7 +300,9 @@ export async function getCustomWorldGalleryDetail(
|
||||
profileId: string,
|
||||
options: RuntimeRequestOptions = {},
|
||||
) {
|
||||
const response = await requestRuntimeJson<CustomWorldGalleryDetailResponse<CustomWorldProfile>>(
|
||||
const response = await requestRuntimeJson<
|
||||
CustomWorldGalleryDetailResponse<CustomWorldProfile>
|
||||
>(
|
||||
`/custom-world-gallery/${encodeURIComponent(ownerUserId)}/${encodeURIComponent(profileId)}`,
|
||||
{ method: 'GET' },
|
||||
'读取作品详情失败',
|
||||
@@ -240,12 +312,79 @@ export async function getCustomWorldGalleryDetail(
|
||||
return response.entry;
|
||||
}
|
||||
|
||||
export async function listProfileBrowseHistory(
|
||||
options: RuntimeRequestOptions = {},
|
||||
) {
|
||||
const response = await requestProfileJson<PlatformBrowseHistoryResponse>(
|
||||
'/browse-history',
|
||||
{ method: 'GET' },
|
||||
'读取浏览历史失败',
|
||||
options,
|
||||
);
|
||||
|
||||
return Array.isArray(response?.entries) ? response.entries : [];
|
||||
}
|
||||
|
||||
export async function upsertProfileBrowseHistory(
|
||||
entry: PlatformBrowseHistoryWriteEntry,
|
||||
options: RuntimeRequestOptions = {},
|
||||
) {
|
||||
const response = await requestProfileJson<PlatformBrowseHistoryResponse>(
|
||||
'/browse-history',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(entry),
|
||||
},
|
||||
'写入浏览历史失败',
|
||||
options,
|
||||
);
|
||||
|
||||
return Array.isArray(response?.entries) ? response.entries : [];
|
||||
}
|
||||
|
||||
export async function syncProfileBrowseHistory(
|
||||
entries: PlatformBrowseHistoryWriteEntry[],
|
||||
options: RuntimeRequestOptions = {},
|
||||
) {
|
||||
const response = await requestProfileJson<PlatformBrowseHistoryResponse>(
|
||||
'/browse-history',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
entries,
|
||||
} satisfies PlatformBrowseHistoryBatchSyncRequest),
|
||||
},
|
||||
'同步浏览历史失败',
|
||||
options,
|
||||
);
|
||||
|
||||
return Array.isArray(response?.entries) ? response.entries : [];
|
||||
}
|
||||
|
||||
export async function clearProfileBrowseHistory(
|
||||
options: RuntimeRequestOptions = {},
|
||||
) {
|
||||
const response = await requestProfileJson<PlatformBrowseHistoryResponse>(
|
||||
'/browse-history',
|
||||
{ method: 'DELETE' },
|
||||
'清空浏览历史失败',
|
||||
options,
|
||||
);
|
||||
|
||||
return Array.isArray(response?.entries) ? response.entries : [];
|
||||
}
|
||||
|
||||
export const runtimeStorageClient = {
|
||||
getSaveSnapshot,
|
||||
putSaveSnapshot,
|
||||
deleteSaveSnapshot,
|
||||
getSettings,
|
||||
putSettings,
|
||||
getProfileDashboard,
|
||||
getProfileWalletLedger,
|
||||
getProfilePlayStats,
|
||||
listCustomWorldLibrary,
|
||||
listCustomWorldWorks,
|
||||
upsertCustomWorldProfile,
|
||||
@@ -254,6 +393,11 @@ export const runtimeStorageClient = {
|
||||
unpublishCustomWorldProfile,
|
||||
listCustomWorldGallery,
|
||||
getCustomWorldGalleryDetail,
|
||||
listProfileBrowseHistory,
|
||||
upsertProfileBrowseHistory,
|
||||
syncProfileBrowseHistory,
|
||||
clearProfileBrowseHistory,
|
||||
};
|
||||
|
||||
export type { CustomWorldLibraryEntry };
|
||||
export type { PlatformBrowseHistoryEntry };
|
||||
|
||||
Reference in New Issue
Block a user