# 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
358 lines
10 KiB
TypeScript
358 lines
10 KiB
TypeScript
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,
|
||
};
|