Files
Genarrative/src/services/jump-hop/jumpHopClient.ts
kdletters 2db3a6e185 Merge remote-tracking branch 'origin/master' into dev-jenken
# Conflicts:
#	.hermes/shared-memory/pitfalls.md
#	server-rs/crates/api-server/src/modules/jump_hop.rs
#	src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx
#	src/services/jump-hop/jumpHopClient.test.ts
2026-06-05 23:59:40 +08:00

358 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type {
JumpHopActionRequest,
JumpHopActionResponse,
JumpHopDraftResponse,
JumpHopGalleryCardResponse,
JumpHopGalleryDetailResponse,
JumpHopGalleryResponse,
JumpHopLeaderboardResponse,
JumpHopRunResponse,
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';
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 = {
dragDistance: number;
dragVectorX?: number;
dragVectorY?: number;
};
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) {
const response = await requestJson<JumpHopWorkDetailResponse>(
`${JUMP_HOP_RUNTIME_API_BASE}/works/${encodeURIComponent(profileId)}`,
{ method: 'GET' },
'读取跳一跳作品详情失败',
);
return normalizeJumpHopWorkDetailResponse(response);
}
export async function listJumpHopGallery() {
return requestJson<JumpHopGalleryResponse>(
`${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<JumpHopGalleryDetailResponse>(
`${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<JumpHopWorksResponse>(
JUMP_HOP_WORKS_API_BASE,
{ method: 'GET' },
'读取跳一跳作品列表失败',
{
retry: JUMP_HOP_RUNTIME_READ_RETRY,
},
);
}
export async function publishJumpHopWork(profileId: string) {
const response = await requestJson<JumpHopWorkMutationResponse>(
`${JUMP_HOP_WORKS_API_BASE}/${encodeURIComponent(profileId)}/publish`,
{ method: 'POST' },
'发布跳一跳作品失败',
);
return normalizeJumpHopWorkMutationResponse(response);
}
export async function deleteJumpHopWork(profileId: string) {
return requestJson<JumpHopWorksResponse>(
`${JUMP_HOP_WORKS_API_BASE}/${encodeURIComponent(profileId)}`,
{ method: 'DELETE' },
'删除跳一跳作品失败',
);
}
export async function startJumpHopRuntimeRun(
profileId: string,
options: JumpHopStartRunOptions = {},
) {
const requestOptions = buildRuntimeGuestAuthOptions(options);
const runtimeMode = options.runtimeMode ?? 'published';
return requestJson<JumpHopRunResponse>(
`${JUMP_HOP_RUNTIME_API_BASE}/runs`,
{
method: 'POST',
headers: {
'content-type': 'application/json',
...buildRuntimeGuestHeaders(options),
},
body: JSON.stringify({ profileId, runtimeMode }),
},
'启动跳一跳运行态失败',
{
...requestOptions,
},
);
}
export async function submitJumpHopJump(
runId: string,
payload: JumpHopJumpPayload,
options: JumpHopRuntimeRequestOptions = {},
) {
const requestOptions = buildRuntimeGuestAuthOptions(options);
const requestPayload = {
dragDistance: payload.dragDistance,
dragVectorX: payload.dragVectorX,
dragVectorY: payload.dragVectorY,
clientEventId: `jump-${runId}-${Date.now()}`,
};
return requestJson<JumpHopRunResponse>(
`${JUMP_HOP_RUNTIME_API_BASE}/runs/${encodeURIComponent(runId)}/jump`,
{
method: 'POST',
headers: {
'content-type': 'application/json',
...buildRuntimeGuestHeaders(options),
},
body: JSON.stringify(requestPayload),
},
'提交跳一跳起跳失败',
requestOptions,
);
}
export async function getJumpHopLeaderboard(
profileId: string,
options: JumpHopRuntimeRequestOptions = {},
) {
const requestOptions = buildRuntimeGuestAuthOptions(options);
return requestJson<JumpHopLeaderboardResponse>(
`${JUMP_HOP_RUNTIME_API_BASE}/works/${encodeURIComponent(profileId)}/leaderboard`,
{
method: 'GET',
headers: buildRuntimeGuestHeaders(options),
},
'读取跳一跳排行榜失败',
requestOptions,
);
}
export async function restartJumpHopRuntimeRun(
runId: string,
options: JumpHopRuntimeRequestOptions = {},
) {
const requestOptions = buildRuntimeGuestAuthOptions(options);
return requestJson<JumpHopRunResponse>(
`${JUMP_HOP_RUNTIME_API_BASE}/runs/${encodeURIComponent(runId)}/restart`,
{
method: 'POST',
headers: {
'content-type': 'application/json',
...buildRuntimeGuestHeaders(options),
},
body: JSON.stringify({
clientActionId: `restart-${runId}-${Date.now()}`,
}),
},
'重新开始跳一跳失败',
requestOptions,
);
}
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,
};