import type { JumpHopActionRequest, JumpHopActionResponse, JumpHopDraftResponse, JumpHopGalleryCardResponse, JumpHopGalleryDetailResponse, JumpHopGalleryResponse, JumpHopJumpRequest, JumpHopLeaderboardResponse, JumpHopRunResponse, JumpHopStartRunRequest, JumpHopRuntimeRunSnapshotResponse, JumpHopSessionResponse, JumpHopSessionSnapshotResponse, JumpHopWorkDetailResponse, JumpHopWorkMutationResponse, JumpHopWorkProfileResponse, JumpHopWorkspaceCreateRequest, JumpHopWorksResponse, JumpHopWorkSummaryResponse, } from '../../../packages/shared/src/contracts/jumpHop'; import { type ApiRetryOptions, requestJson, } from '../apiClient'; import { createCreationAgentClient } from '../creation-agent'; import { buildRuntimeGuestAuthOptions, buildRuntimeGuestHeaders, type RuntimeGuestRequestOptions, } from '../runtimeGuestAuth'; import { buildRuntimeApiPath, requestRuntimeJson } from '../runtimeRequest'; const JUMP_HOP_API_BASE = '/api/creation/jump-hop/sessions'; const JUMP_HOP_WORKS_API_BASE = '/api/creation/jump-hop/works'; const JUMP_HOP_RUNTIME_API_BASE = '/api/runtime/jump-hop'; // 中文注释:跳一跳创作会等待背景图、25 格图集、切片和 OSS 写入,不能沿用共创会话默认 15 秒超时。 const JUMP_HOP_GENERATION_TIMEOUT_MS = 20 * 60 * 1000; const JUMP_HOP_RUNTIME_READ_RETRY: ApiRetryOptions = { maxRetries: 1, baseDelayMs: 120, maxDelayMs: 360, }; export type JumpHopRuntimeRequestOptions = RuntimeGuestRequestOptions; type JumpHopRuntimeMode = 'draft' | 'published'; type JumpHopStartRunOptions = JumpHopRuntimeRequestOptions & { runtimeMode?: JumpHopRuntimeMode; }; type JumpHopJumpPayload = Pick< JumpHopJumpRequest, 'dragDistance' >; export type { JumpHopActionRequest, JumpHopActionResponse, JumpHopDraftResponse, JumpHopGalleryCardResponse, JumpHopGalleryDetailResponse, JumpHopGalleryResponse, JumpHopLeaderboardResponse, JumpHopRunResponse, JumpHopRuntimeRunSnapshotResponse, JumpHopSessionResponse, JumpHopSessionSnapshotResponse, JumpHopWorkDetailResponse, JumpHopWorkMutationResponse, JumpHopWorkProfileResponse, JumpHopWorkspaceCreateRequest, JumpHopWorksResponse, }; export type CreateJumpHopSessionRequest = JumpHopWorkspaceCreateRequest; export type ExecuteJumpHopActionRequest = JumpHopActionRequest; export type JumpHopSessionSnapshot = JumpHopSessionSnapshotResponse; const jumpHopCreationClient = createCreationAgentClient< JumpHopWorkspaceCreateRequest, JumpHopSessionResponse, JumpHopSessionResponse, JumpHopSessionSnapshotResponse, never, never, JumpHopActionRequest, JumpHopActionResponse >({ apiBase: JUMP_HOP_API_BASE, messages: { createSession: '创建跳一跳共创会话失败', getSession: '读取跳一跳共创会话失败', sendMessage: '发送跳一跳共创消息失败', streamIncomplete: '跳一跳共创消息流式结果不完整', executeAction: '执行跳一跳共创操作失败', }, createSessionTimeoutMs: JUMP_HOP_GENERATION_TIMEOUT_MS, executeActionTimeoutMs: JUMP_HOP_GENERATION_TIMEOUT_MS, }); type FlattenedJumpHopWorkProfileResponse = Omit< JumpHopWorkProfileResponse, 'summary' > & JumpHopWorkSummaryResponse; function normalizeJumpHopWorkProfile( work: JumpHopWorkProfileResponse | FlattenedJumpHopWorkProfileResponse, ): JumpHopWorkProfileResponse { if ('summary' in work && work.summary) { return work; } const flattened = work as FlattenedJumpHopWorkProfileResponse; const summary: JumpHopWorkProfileResponse['summary'] = { runtimeKind: flattened.runtimeKind, workId: flattened.workId, profileId: flattened.profileId, ownerUserId: flattened.ownerUserId, sourceSessionId: flattened.sourceSessionId ?? null, themeText: flattened.themeText || flattened.workTitle, workTitle: flattened.workTitle, workDescription: flattened.workDescription, themeTags: flattened.themeTags, difficulty: flattened.difficulty, stylePreset: flattened.stylePreset, coverImageSrc: flattened.coverImageSrc ?? null, publicationStatus: flattened.publicationStatus, playCount: flattened.playCount, updatedAt: flattened.updatedAt, publishedAt: flattened.publishedAt ?? null, publishReady: flattened.publishReady, generationStatus: flattened.generationStatus, }; return { summary, draft: flattened.draft, path: flattened.path, defaultCharacter: flattened.defaultCharacter ?? flattened.draft?.defaultCharacter, characterAsset: flattened.characterAsset, tileAtlasAsset: flattened.tileAtlasAsset, tileAssets: flattened.tileAssets, backButtonAsset: flattened.backButtonAsset ?? flattened.draft?.backButtonAsset ?? null, }; } function normalizeJumpHopActionResponse( response: JumpHopActionResponse, ): JumpHopActionResponse { return { ...response, work: response.work ? normalizeJumpHopWorkProfile(response.work) : null, }; } function normalizeJumpHopWorkDetailResponse( response: JumpHopWorkDetailResponse, ): JumpHopWorkDetailResponse { return { ...response, item: normalizeJumpHopWorkProfile(response.item), }; } function normalizeJumpHopWorkMutationResponse( response: JumpHopWorkMutationResponse, ): JumpHopWorkMutationResponse { return { ...response, item: normalizeJumpHopWorkProfile(response.item), }; } export function createJumpHopCreationSession( payload: JumpHopWorkspaceCreateRequest, ) { return jumpHopCreationClient.createSession(payload); } export function getJumpHopCreationSession(sessionId: string) { return jumpHopCreationClient.getSession(sessionId); } export function executeJumpHopCreationAction( sessionId: string, payload: JumpHopActionRequest, ) { return jumpHopCreationClient .executeAction(sessionId, payload) .then(normalizeJumpHopActionResponse); } export async function getJumpHopWorkDetail( profileId: string, options: { audience?: 'creation' | 'runtime' } = {}, ) { const base = options.audience === 'creation' ? JUMP_HOP_WORKS_API_BASE : `${JUMP_HOP_RUNTIME_API_BASE}/works`; const response = await requestJson( `${base}/${encodeURIComponent(profileId)}`, { method: 'GET' }, '读取跳一跳作品详情失败', ); return normalizeJumpHopWorkDetailResponse(response); } export async function listJumpHopGallery() { return requestJson( `${JUMP_HOP_RUNTIME_API_BASE}/gallery`, { method: 'GET' }, '读取跳一跳广场失败', { retry: JUMP_HOP_RUNTIME_READ_RETRY, skipAuth: true, skipRefresh: true, }, ); } export async function getJumpHopGalleryDetail(publicWorkCode: string) { const response = await requestJson( `${JUMP_HOP_RUNTIME_API_BASE}/gallery/${encodeURIComponent(publicWorkCode)}`, { method: 'GET' }, '读取跳一跳广场详情失败', { retry: JUMP_HOP_RUNTIME_READ_RETRY, skipAuth: true, skipRefresh: true, }, ); return normalizeJumpHopWorkDetailResponse(response); } export async function listJumpHopWorks() { return requestJson( JUMP_HOP_WORKS_API_BASE, { method: 'GET' }, '读取跳一跳作品列表失败', { retry: JUMP_HOP_RUNTIME_READ_RETRY, }, ); } export async function publishJumpHopWork(profileId: string) { const response = await requestJson( `${JUMP_HOP_WORKS_API_BASE}/${encodeURIComponent(profileId)}/publish`, { method: 'POST' }, '发布跳一跳作品失败', ); return normalizeJumpHopWorkMutationResponse(response); } export async function deleteJumpHopWork(profileId: string) { return requestJson( `${JUMP_HOP_WORKS_API_BASE}/${encodeURIComponent(profileId)}`, { method: 'DELETE' }, '删除跳一跳作品失败', ); } export async function startJumpHopRuntimeRun( profileId: string, options: JumpHopStartRunOptions = {}, ) { const requestBody: JumpHopStartRunRequest = { profileId, ...(options.runtimeMode ? { runtimeMode: options.runtimeMode } : {}), }; return requestRuntimeJson({ url: buildRuntimeApiPath(JUMP_HOP_RUNTIME_API_BASE, 'runs'), method: 'POST', jsonBody: requestBody, fallbackMessage: '启动跳一跳运行态失败', requestOptions: options, }); } export async function submitJumpHopJump( runId: string, payload: JumpHopJumpPayload, options: JumpHopRuntimeRequestOptions = {}, ) { const requestPayload = { dragDistance: payload.dragDistance, clientEventId: `jump-${runId}-${Date.now()}`, }; return requestRuntimeJson({ url: buildRuntimeApiPath(JUMP_HOP_RUNTIME_API_BASE, 'runs', runId, 'jump'), method: 'POST', jsonBody: requestPayload, fallbackMessage: '提交跳一跳起跳失败', requestOptions: options, }); } export async function getJumpHopLeaderboard( profileId: string, options: JumpHopRuntimeRequestOptions = {}, ) { const requestOptions = buildRuntimeGuestAuthOptions(options); return requestJson( `${JUMP_HOP_RUNTIME_API_BASE}/works/${encodeURIComponent(profileId)}/leaderboard`, { method: 'GET', headers: buildRuntimeGuestHeaders(options), }, '读取跳一跳排行榜失败', requestOptions, ); } export async function restartJumpHopRuntimeRun( runId: string, options: JumpHopRuntimeRequestOptions = {}, ) { return requestRuntimeJson({ url: buildRuntimeApiPath( JUMP_HOP_RUNTIME_API_BASE, 'runs', runId, 'restart', ), method: 'POST', jsonBody: { clientActionId: `restart-${runId}-${Date.now()}`, }, fallbackMessage: '重新开始跳一跳失败', requestOptions: options, }); } export const jumpHopClient = { createSession: createJumpHopCreationSession, deleteWork: deleteJumpHopWork, getSession: getJumpHopCreationSession, executeAction: executeJumpHopCreationAction, getGalleryDetail: getJumpHopGalleryDetail, getWorkDetail: getJumpHopWorkDetail, listGallery: listJumpHopGallery, listWorks: listJumpHopWorks, publishWork: publishJumpHopWork, getLeaderboard: getJumpHopLeaderboard, restartRun: restartJumpHopRuntimeRun, startRun: startJumpHopRuntimeRun, submitJump: submitJumpHopJump, };