@@ -9,6 +9,12 @@ import type {
|
||||
CustomWorldGalleryResponse,
|
||||
CustomWorldLibraryMutationResponse,
|
||||
CustomWorldLibraryResponse,
|
||||
PlatformBrowseHistoryBatchSyncRequest,
|
||||
PlatformBrowseHistoryResponse,
|
||||
PlatformBrowseHistoryWriteEntry,
|
||||
ProfileDashboardSummary,
|
||||
ProfilePlayStatsResponse,
|
||||
ProfileWalletLedgerResponse,
|
||||
RuntimeSettings,
|
||||
SavedGameSnapshotInput,
|
||||
} from '../../../packages/shared/src/contracts/runtime.js';
|
||||
@@ -52,10 +58,10 @@ import {
|
||||
npcChatDialogueRequestSchema,
|
||||
npcRecruitDialogueRequestSchema,
|
||||
} from '../services/chatService.js';
|
||||
import { generateCustomWorldEntity } from '../services/customWorldEntityGenerationService.js';
|
||||
import { generateCustomWorldProfile } from '../services/customWorldGenerationService.js';
|
||||
import {
|
||||
listCustomWorldWorkSummaries,
|
||||
} from '../services/customWorldWorkSummaryService.js';
|
||||
import { generateSceneNpcForLandmark } from '../services/customWorldSceneNpcGenerationService.js';
|
||||
import { listCustomWorldWorkSummaries } from '../services/customWorldWorkSummaryService.js';
|
||||
import { generateQuestForNpcEncounter } from '../services/questService.js';
|
||||
import { generateRuntimeItemIntents } from '../services/runtimeItemService.js';
|
||||
import {
|
||||
@@ -82,10 +88,36 @@ const settingsSchema = z.object({
|
||||
musicVolume: z.number().min(0).max(1),
|
||||
});
|
||||
|
||||
const platformBrowseHistoryEntrySchema = z.object({
|
||||
ownerUserId: z.string().trim().min(1),
|
||||
profileId: z.string().trim().min(1),
|
||||
worldName: z.string().trim().min(1),
|
||||
subtitle: z.string().trim().optional().default(''),
|
||||
summaryText: z.string().trim().optional().default(''),
|
||||
coverImageSrc: z.string().trim().nullable().optional().default(null),
|
||||
themeMode: z.string().trim().optional().default('mythic'),
|
||||
authorDisplayName: z.string().trim().optional().default('玩家'),
|
||||
visitedAt: z.string().trim().optional().default(''),
|
||||
});
|
||||
|
||||
const platformBrowseHistoryBatchSchema = z.object({
|
||||
entries: z.array(platformBrowseHistoryEntrySchema).max(100),
|
||||
});
|
||||
|
||||
const customWorldProfileSchema = z.object({
|
||||
profile: jsonObjectSchema,
|
||||
});
|
||||
|
||||
const customWorldSceneNpcSchema = z.object({
|
||||
profile: jsonObjectSchema,
|
||||
landmarkId: z.string().trim().min(1),
|
||||
});
|
||||
|
||||
const customWorldEntitySchema = z.object({
|
||||
profile: jsonObjectSchema,
|
||||
kind: z.enum(['playable', 'story', 'landmark']),
|
||||
});
|
||||
|
||||
const customWorldSessionSchema = z.object({
|
||||
settingText: z.string().trim().min(1),
|
||||
creatorIntent: jsonObjectSchema.nullable().optional().default(null),
|
||||
@@ -125,6 +157,29 @@ async function resolveAuthDisplayName(context: AppContext, userId: string) {
|
||||
export function createRuntimeRoutes(context: AppContext) {
|
||||
const router = Router();
|
||||
const requireAuth = requireJwtAuth(context.config, context.userRepository);
|
||||
const routeCompatPaths = (path: string) => [
|
||||
path,
|
||||
`/runtime${path}`,
|
||||
] as const;
|
||||
const handleCustomWorldEntityGeneration = asyncHandler(async (request, response) => {
|
||||
const payload = customWorldEntitySchema.parse(request.body) as {
|
||||
profile: Record<string, unknown>;
|
||||
kind: 'playable' | 'story' | 'landmark';
|
||||
};
|
||||
sendApiResponse(
|
||||
response,
|
||||
await generateCustomWorldEntity(context.llmClient, payload),
|
||||
);
|
||||
});
|
||||
const handleCustomWorldSceneNpcGeneration = asyncHandler(async (request, response) => {
|
||||
const payload = customWorldSceneNpcSchema.parse(request.body) as {
|
||||
profile: Record<string, unknown>;
|
||||
landmarkId: string;
|
||||
};
|
||||
sendApiResponse(response, {
|
||||
npc: await generateSceneNpcForLandmark(context.llmClient, payload),
|
||||
});
|
||||
});
|
||||
|
||||
router.use(requireAuth);
|
||||
router.use(
|
||||
@@ -132,6 +187,129 @@ export function createRuntimeRoutes(context: AppContext) {
|
||||
createCustomWorldAgentRoutes(context),
|
||||
);
|
||||
|
||||
routeCompatPaths('/profile/dashboard').forEach((path, index) => {
|
||||
router.get(
|
||||
path,
|
||||
routeMeta({
|
||||
operation:
|
||||
index === 0
|
||||
? 'profile.dashboard.get'
|
||||
: 'profile.dashboard.get.compat',
|
||||
}),
|
||||
asyncHandler(async (request, response) => {
|
||||
sendApiResponse<ProfileDashboardSummary>(
|
||||
response,
|
||||
await context.runtimeRepository.getProfileDashboard(request.userId!),
|
||||
);
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
routeCompatPaths('/profile/wallet-ledger').forEach((path, index) => {
|
||||
router.get(
|
||||
path,
|
||||
routeMeta({
|
||||
operation:
|
||||
index === 0
|
||||
? 'profile.walletLedger.list'
|
||||
: 'profile.walletLedger.list.compat',
|
||||
}),
|
||||
asyncHandler(async (request, response) => {
|
||||
sendApiResponse<ProfileWalletLedgerResponse>(response, {
|
||||
entries: await context.runtimeRepository.listProfileWalletLedger(
|
||||
request.userId!,
|
||||
),
|
||||
});
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
routeCompatPaths('/profile/play-stats').forEach((path, index) => {
|
||||
router.get(
|
||||
path,
|
||||
routeMeta({
|
||||
operation:
|
||||
index === 0
|
||||
? 'profile.playStats.get'
|
||||
: 'profile.playStats.get.compat',
|
||||
}),
|
||||
asyncHandler(async (request, response) => {
|
||||
sendApiResponse<ProfilePlayStatsResponse>(
|
||||
response,
|
||||
await context.runtimeRepository.getProfilePlayStats(request.userId!),
|
||||
);
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
routeCompatPaths('/profile/browse-history').forEach((path, index) => {
|
||||
router.get(
|
||||
path,
|
||||
routeMeta({
|
||||
operation:
|
||||
index === 0
|
||||
? 'profile.browseHistory.list'
|
||||
: 'profile.browseHistory.list.compat',
|
||||
}),
|
||||
asyncHandler(async (request, response) => {
|
||||
sendApiResponse<PlatformBrowseHistoryResponse>(response, {
|
||||
entries: await context.runtimeRepository.listPlatformBrowseHistory(
|
||||
request.userId!,
|
||||
),
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
path,
|
||||
routeMeta({
|
||||
operation:
|
||||
index === 0
|
||||
? 'profile.browseHistory.upsert'
|
||||
: 'profile.browseHistory.upsert.compat',
|
||||
}),
|
||||
asyncHandler(async (request, response) => {
|
||||
const rawBody =
|
||||
request.body && typeof request.body === 'object' ? request.body : {};
|
||||
const payload = (
|
||||
'entries' in rawBody
|
||||
? platformBrowseHistoryBatchSchema.parse(rawBody)
|
||||
: platformBrowseHistoryEntrySchema.parse(rawBody)
|
||||
) as
|
||||
| PlatformBrowseHistoryBatchSyncRequest
|
||||
| PlatformBrowseHistoryWriteEntry;
|
||||
|
||||
const entries = 'entries' in payload ? payload.entries : [payload];
|
||||
|
||||
sendApiResponse<PlatformBrowseHistoryResponse>(response, {
|
||||
entries:
|
||||
await context.runtimeRepository.upsertPlatformBrowseHistoryEntries(
|
||||
request.userId!,
|
||||
entries,
|
||||
),
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
router.delete(
|
||||
path,
|
||||
routeMeta({
|
||||
operation:
|
||||
index === 0
|
||||
? 'profile.browseHistory.clear'
|
||||
: 'profile.browseHistory.clear.compat',
|
||||
}),
|
||||
asyncHandler(async (request, response) => {
|
||||
await context.runtimeRepository.clearPlatformBrowseHistory(
|
||||
request.userId!,
|
||||
);
|
||||
sendApiResponse<PlatformBrowseHistoryResponse>(response, {
|
||||
entries: [],
|
||||
});
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
router.post(
|
||||
'/llm/chat/completions',
|
||||
routeMeta({ operation: 'runtime.llm.chatCompletionsProxy' }),
|
||||
@@ -150,6 +328,30 @@ export function createRuntimeRoutes(context: AppContext) {
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/custom-world/entity',
|
||||
routeMeta({ operation: 'runtime.customWorld.entity' }),
|
||||
handleCustomWorldEntityGeneration,
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/runtime/custom-world/entity',
|
||||
routeMeta({ operation: 'runtime.customWorld.entity.compat' }),
|
||||
handleCustomWorldEntityGeneration,
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/custom-world/scene-npc',
|
||||
routeMeta({ operation: 'runtime.customWorld.sceneNpc' }),
|
||||
handleCustomWorldSceneNpcGeneration,
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/runtime/custom-world/scene-npc',
|
||||
routeMeta({ operation: 'runtime.customWorld.sceneNpc.compat' }),
|
||||
handleCustomWorldSceneNpcGeneration,
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/runtime/save/snapshot',
|
||||
routeMeta({ operation: 'runtime.snapshot.get' }),
|
||||
@@ -237,14 +439,11 @@ export function createRuntimeRoutes(context: AppContext) {
|
||||
'/runtime/custom-world-library',
|
||||
routeMeta({ operation: 'runtime.customWorldLibrary.list' }),
|
||||
asyncHandler(async (request, response) => {
|
||||
sendApiResponse(
|
||||
response,
|
||||
{
|
||||
entries: await context.runtimeRepository.listCustomWorldProfiles(
|
||||
request.userId!,
|
||||
),
|
||||
} satisfies CustomWorldLibraryResponse,
|
||||
);
|
||||
sendApiResponse(response, {
|
||||
entries: await context.runtimeRepository.listCustomWorldProfiles(
|
||||
request.userId!,
|
||||
),
|
||||
} satisfies CustomWorldLibraryResponse);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -252,12 +451,10 @@ export function createRuntimeRoutes(context: AppContext) {
|
||||
'/runtime/custom-world-gallery',
|
||||
routeMeta({ operation: 'runtime.customWorldGallery.list' }),
|
||||
asyncHandler(async (_request, response) => {
|
||||
sendApiResponse(
|
||||
response,
|
||||
{
|
||||
entries: await context.runtimeRepository.listPublishedCustomWorldGallery(),
|
||||
} satisfies CustomWorldGalleryResponse,
|
||||
);
|
||||
sendApiResponse(response, {
|
||||
entries:
|
||||
await context.runtimeRepository.listPublishedCustomWorldGallery(),
|
||||
} satisfies CustomWorldGalleryResponse);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -280,12 +477,9 @@ export function createRuntimeRoutes(context: AppContext) {
|
||||
throw notFound('public custom world not found');
|
||||
}
|
||||
|
||||
sendApiResponse(
|
||||
response,
|
||||
{
|
||||
entry,
|
||||
} satisfies CustomWorldGalleryDetailResponse,
|
||||
);
|
||||
sendApiResponse(response, {
|
||||
entry,
|
||||
} satisfies CustomWorldGalleryDetailResponse);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -322,15 +516,12 @@ export function createRuntimeRoutes(context: AppContext) {
|
||||
if (!profileId) {
|
||||
throw badRequest('profileId is required');
|
||||
}
|
||||
sendApiResponse(
|
||||
response,
|
||||
{
|
||||
entries: await context.runtimeRepository.deleteCustomWorldProfile(
|
||||
request.userId!,
|
||||
profileId,
|
||||
),
|
||||
} satisfies CustomWorldLibraryResponse,
|
||||
);
|
||||
sendApiResponse(response, {
|
||||
entries: await context.runtimeRepository.deleteCustomWorldProfile(
|
||||
request.userId!,
|
||||
profileId,
|
||||
),
|
||||
} satisfies CustomWorldLibraryResponse);
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user