Files
Genarrative/src/services/wooden-fish/woodenFishClient.ts
kdletters 5859d738a0 Merge remote-tracking branch 'origin/master' into feat/recommend-runtime-guest
# Conflicts:
#	docs/【玩法创作】平台入口与玩法链路-2026-05-15.md
2026-05-25 14:12:39 +08:00

318 lines
9.6 KiB
TypeScript

import type {
WoodenFishActionRequest,
WoodenFishActionResponse,
WoodenFishCheckpointRunRequest,
WoodenFishFinishRunRequest,
WoodenFishGalleryCardResponse,
WoodenFishGalleryDetailResponse,
WoodenFishGalleryResponse,
WoodenFishRunResponse,
WoodenFishRuntimeRunSnapshotResponse,
WoodenFishSessionResponse,
WoodenFishSessionSnapshotResponse,
WoodenFishWorkDetailResponse,
WoodenFishWorkMutationResponse,
WoodenFishWorkProfileResponse,
WoodenFishWorkspaceCreateRequest,
WoodenFishWorkSummaryResponse,
} from '../../../packages/shared/src/contracts/woodenFish';
import { type ApiRetryOptions, requestJson } from '../apiClient';
import { createCreationAgentClient } from '../creation-agent';
import {
buildRuntimeGuestAuthOptions,
buildRuntimeGuestHeaders,
type RuntimeGuestRequestOptions,
} from '../runtimeGuestAuth';
const WOODEN_FISH_API_BASE = '/api/creation/wooden-fish/sessions';
const WOODEN_FISH_WORKS_API_BASE = '/api/creation/wooden-fish/works';
const WOODEN_FISH_RUNTIME_API_BASE = '/api/runtime/wooden-fish';
// 中文注释:敲木鱼创作会串行等待多次 image2 与 OSS 写入,前端请求窗口需要覆盖完整生成链路。
const WOODEN_FISH_GENERATION_TIMEOUT_MS = 20 * 60 * 1000;
const WOODEN_FISH_RUNTIME_READ_RETRY: ApiRetryOptions = {
maxRetries: 1,
baseDelayMs: 120,
maxDelayMs: 360,
};
const WOODEN_FISH_RUNTIME_WRITE_RETRY: ApiRetryOptions = {
maxRetries: 1,
baseDelayMs: 120,
maxDelayMs: 360,
retryUnsafeMethods: true,
};
type WoodenFishRuntimeRequestOptions = RuntimeGuestRequestOptions;
export type {
WoodenFishActionRequest,
WoodenFishActionResponse,
WoodenFishCheckpointRunRequest,
WoodenFishFinishRunRequest,
WoodenFishGalleryCardResponse,
WoodenFishGalleryDetailResponse,
WoodenFishGalleryResponse,
WoodenFishRunResponse,
WoodenFishRuntimeRunSnapshotResponse,
WoodenFishSessionResponse,
WoodenFishSessionSnapshotResponse,
WoodenFishWorkDetailResponse,
WoodenFishWorkMutationResponse,
WoodenFishWorkProfileResponse,
WoodenFishWorkspaceCreateRequest,
};
export type CreateWoodenFishSessionRequest = WoodenFishWorkspaceCreateRequest;
export type WoodenFishSessionSnapshot = WoodenFishSessionSnapshotResponse;
const woodenFishCreationClient = createCreationAgentClient<
WoodenFishWorkspaceCreateRequest,
WoodenFishSessionResponse,
WoodenFishSessionResponse,
WoodenFishSessionSnapshotResponse,
never,
never,
WoodenFishActionRequest,
WoodenFishActionResponse
>({
apiBase: WOODEN_FISH_API_BASE,
messages: {
createSession: '创建敲木鱼共创会话失败',
getSession: '读取敲木鱼共创会话失败',
sendMessage: '发送敲木鱼共创消息失败',
streamIncomplete: '敲木鱼共创消息流式结果不完整',
executeAction: '执行敲木鱼共创操作失败',
},
createSessionTimeoutMs: WOODEN_FISH_GENERATION_TIMEOUT_MS,
executeActionTimeoutMs: WOODEN_FISH_GENERATION_TIMEOUT_MS,
});
type FlattenedWoodenFishWorkProfileResponse = Omit<
WoodenFishWorkProfileResponse,
'summary'
> &
WoodenFishWorkSummaryResponse;
function normalizeWoodenFishWorkProfile(
work:
| WoodenFishWorkProfileResponse
| FlattenedWoodenFishWorkProfileResponse,
): WoodenFishWorkProfileResponse {
if ('summary' in work && work.summary) {
return work;
}
const flattened = work as FlattenedWoodenFishWorkProfileResponse;
const summary: WoodenFishWorkProfileResponse['summary'] = {
runtimeKind: flattened.runtimeKind,
workId: flattened.workId,
profileId: flattened.profileId,
ownerUserId: flattened.ownerUserId,
sourceSessionId: flattened.sourceSessionId ?? null,
workTitle: flattened.workTitle,
workDescription: flattened.workDescription,
themeTags: flattened.themeTags,
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,
hitObjectAsset: flattened.hitObjectAsset,
backgroundAsset:
flattened.backgroundAsset ?? flattened.draft?.backgroundAsset ?? null,
backButtonAsset:
flattened.backButtonAsset ?? flattened.draft?.backButtonAsset ?? null,
hitSoundAsset: flattened.hitSoundAsset,
floatingWords: flattened.floatingWords,
};
}
function normalizeWoodenFishActionResponse(
response: WoodenFishActionResponse,
): WoodenFishActionResponse {
return {
...response,
work: response.work ? normalizeWoodenFishWorkProfile(response.work) : null,
};
}
function normalizeWoodenFishWorkDetailResponse(
response: WoodenFishWorkDetailResponse,
): WoodenFishWorkDetailResponse {
return {
...response,
item: normalizeWoodenFishWorkProfile(response.item),
};
}
function normalizeWoodenFishWorkMutationResponse(
response: WoodenFishWorkMutationResponse,
): WoodenFishWorkMutationResponse {
return {
...response,
item: normalizeWoodenFishWorkProfile(response.item),
};
}
export function createWoodenFishCreationSession(
payload: WoodenFishWorkspaceCreateRequest,
) {
return woodenFishCreationClient.createSession(payload);
}
export function getWoodenFishCreationSession(sessionId: string) {
return woodenFishCreationClient.getSession(sessionId);
}
export function executeWoodenFishCreationAction(
sessionId: string,
payload: WoodenFishActionRequest,
) {
return woodenFishCreationClient
.executeAction(sessionId, payload)
.then(normalizeWoodenFishActionResponse);
}
export async function getWoodenFishWorkDetail(profileId: string) {
const response = await requestJson<WoodenFishWorkDetailResponse>(
`${WOODEN_FISH_RUNTIME_API_BASE}/works/${encodeURIComponent(profileId)}`,
{ method: 'GET' },
'读取敲木鱼作品详情失败',
);
return normalizeWoodenFishWorkDetailResponse(response);
}
export async function listWoodenFishGallery() {
return requestJson<WoodenFishGalleryResponse>(
`${WOODEN_FISH_RUNTIME_API_BASE}/gallery`,
{ method: 'GET' },
'读取敲木鱼广场失败',
{
retry: WOODEN_FISH_RUNTIME_READ_RETRY,
skipAuth: true,
skipRefresh: true,
},
);
}
export async function getWoodenFishGalleryDetail(publicWorkCode: string) {
const response = await requestJson<WoodenFishGalleryDetailResponse>(
`${WOODEN_FISH_RUNTIME_API_BASE}/gallery/${encodeURIComponent(publicWorkCode)}`,
{ method: 'GET' },
'读取敲木鱼广场详情失败',
{
retry: WOODEN_FISH_RUNTIME_READ_RETRY,
skipAuth: true,
skipRefresh: true,
},
);
return normalizeWoodenFishWorkDetailResponse(response);
}
export async function publishWoodenFishWork(profileId: string) {
const response = await requestJson<WoodenFishWorkMutationResponse>(
`${WOODEN_FISH_WORKS_API_BASE}/${encodeURIComponent(profileId)}/publish`,
{ method: 'POST' },
'发布敲木鱼作品失败',
);
return normalizeWoodenFishWorkMutationResponse(response);
}
export async function startWoodenFishRuntimeRun(
profileId: string,
options: WoodenFishRuntimeRequestOptions = {},
) {
const requestOptions = buildRuntimeGuestAuthOptions(options);
return requestJson<WoodenFishRunResponse>(
`${WOODEN_FISH_RUNTIME_API_BASE}/runs`,
{
method: 'POST',
headers: {
'content-type': 'application/json',
...buildRuntimeGuestHeaders(options),
},
body: JSON.stringify({ profileId }),
},
'启动敲木鱼运行态失败',
{
retry: WOODEN_FISH_RUNTIME_WRITE_RETRY,
...requestOptions,
},
);
}
export async function checkpointWoodenFishRun(
runId: string,
payload: Omit<WoodenFishCheckpointRunRequest, 'clientEventId'>,
options: WoodenFishRuntimeRequestOptions = {},
) {
const requestOptions = buildRuntimeGuestAuthOptions(options);
const requestPayload: WoodenFishCheckpointRunRequest = {
...payload,
clientEventId: `checkpoint-${runId}-${Date.now()}`,
};
return requestJson<WoodenFishRunResponse>(
`${WOODEN_FISH_RUNTIME_API_BASE}/runs/${encodeURIComponent(runId)}/checkpoint`,
{
method: 'POST',
headers: {
'content-type': 'application/json',
...buildRuntimeGuestHeaders(options),
},
body: JSON.stringify(requestPayload),
},
'保存敲木鱼进度失败',
{
retry: WOODEN_FISH_RUNTIME_WRITE_RETRY,
...requestOptions,
},
);
}
export async function finishWoodenFishRun(
runId: string,
payload: Omit<WoodenFishFinishRunRequest, 'clientEventId'>,
options: WoodenFishRuntimeRequestOptions = {},
) {
const requestOptions = buildRuntimeGuestAuthOptions(options);
const requestPayload: WoodenFishFinishRunRequest = {
...payload,
clientEventId: `finish-${runId}-${Date.now()}`,
};
return requestJson<WoodenFishRunResponse>(
`${WOODEN_FISH_RUNTIME_API_BASE}/runs/${encodeURIComponent(runId)}/finish`,
{
method: 'POST',
headers: {
'content-type': 'application/json',
...buildRuntimeGuestHeaders(options),
},
body: JSON.stringify(requestPayload),
},
'结束敲木鱼运行失败',
{
retry: WOODEN_FISH_RUNTIME_WRITE_RETRY,
...requestOptions,
},
);
}
export const woodenFishClient = {
checkpointRun: checkpointWoodenFishRun,
createSession: createWoodenFishCreationSession,
executeAction: executeWoodenFishCreationAction,
finishRun: finishWoodenFishRun,
getGalleryDetail: getWoodenFishGalleryDetail,
getSession: getWoodenFishCreationSession,
getWorkDetail: getWoodenFishWorkDetail,
listGallery: listWoodenFishGallery,
publishWork: publishWoodenFishWork,
startRun: startWoodenFishRuntimeRun,
};