377 lines
10 KiB
TypeScript
377 lines
10 KiB
TypeScript
import {
|
|
ASSET_API_PATHS,
|
|
postApiJson,
|
|
} from '../../editor/shared/editorApiClient';
|
|
import {
|
|
appendApiErrorRequestId,
|
|
fetchJson,
|
|
parseApiErrorMessage,
|
|
} from '../../editor/shared/jsonClient';
|
|
|
|
export const CHARACTER_VISUAL_GENERATE_API_PATH =
|
|
ASSET_API_PATHS.characterVisualGenerate;
|
|
export const CHARACTER_WORKFLOW_CACHE_API_PATH =
|
|
ASSET_API_PATHS.characterWorkflowCache;
|
|
export const CHARACTER_VISUAL_PUBLISH_API_PATH =
|
|
ASSET_API_PATHS.characterVisualPublish;
|
|
export const CHARACTER_VISUAL_JOB_API_PATH = ASSET_API_PATHS.characterVisualJobs;
|
|
export const CHARACTER_ANIMATION_GENERATE_API_PATH =
|
|
ASSET_API_PATHS.characterAnimationGenerate;
|
|
export const CHARACTER_ANIMATION_PUBLISH_API_PATH =
|
|
ASSET_API_PATHS.characterAnimationPublish;
|
|
export const CHARACTER_ANIMATION_JOB_API_PATH =
|
|
ASSET_API_PATHS.characterAnimationJobs;
|
|
export const CHARACTER_ANIMATION_IMPORT_VIDEO_API_PATH =
|
|
ASSET_API_PATHS.characterAnimationImportVideo;
|
|
export const CHARACTER_ANIMATION_TEMPLATES_API_PATH =
|
|
ASSET_API_PATHS.characterAnimationTemplates;
|
|
export const ROLE_ASSET_WORKFLOW_API_PATH =
|
|
'/api/runtime/custom-world/asset-studio/role';
|
|
|
|
export type CharacterVisualSourceMode =
|
|
| 'text-to-image'
|
|
| 'image-to-image'
|
|
| 'upload';
|
|
|
|
export type CharacterAnimationStrategy =
|
|
| 'image-sequence'
|
|
| 'image-to-video'
|
|
| 'motion-transfer'
|
|
| 'reference-to-video';
|
|
|
|
export type CharacterMotionTransferModel =
|
|
| 'wan2.2-animate-move'
|
|
| 'wan2.2-animate-mix';
|
|
|
|
export type CharacterVisualDraft = {
|
|
id: string;
|
|
label: string;
|
|
imageSrc: string;
|
|
width: number;
|
|
height: number;
|
|
};
|
|
|
|
export type CharacterAssetWorkflowCache = {
|
|
characterId: string;
|
|
cacheScopeId?: string;
|
|
visualPromptText: string;
|
|
animationPromptText: string;
|
|
animationPromptTextByKey?: Record<string, string>;
|
|
visualDrafts: CharacterVisualDraft[];
|
|
selectedVisualDraftId: string;
|
|
selectedAnimation: string;
|
|
imageSrc?: string;
|
|
generatedVisualAssetId?: string;
|
|
generatedAnimationSetId?: string;
|
|
animationMap?: Record<string, unknown> | null;
|
|
updatedAt?: string;
|
|
};
|
|
|
|
export type CharacterAssetRolePromptInput = {
|
|
id: string;
|
|
name?: string;
|
|
title?: string;
|
|
role?: string;
|
|
visualDescription?: string;
|
|
actionDescription?: string;
|
|
sceneVisualDescription?: string;
|
|
description?: string;
|
|
backstory?: string;
|
|
personality?: string;
|
|
motivation?: string;
|
|
combatStyle?: string;
|
|
tags?: string[];
|
|
imageSrc?: string;
|
|
generatedVisualAssetId?: string;
|
|
generatedAnimationSetId?: string;
|
|
animationMap?: Record<string, unknown> | null;
|
|
};
|
|
|
|
export type CharacterRolePromptBundle = {
|
|
visualPromptText: string;
|
|
animationPromptText: string;
|
|
scenePromptText: string;
|
|
};
|
|
|
|
export type CharacterRoleAssetWorkflow = {
|
|
role: CharacterAssetRolePromptInput;
|
|
defaultPromptBundle: CharacterRolePromptBundle;
|
|
visualPromptText: string;
|
|
animationPromptText: string;
|
|
animationPromptTextByKey: Record<string, string>;
|
|
visualDrafts: CharacterVisualDraft[];
|
|
selectedVisualDraftId: string;
|
|
selectedAnimation: string;
|
|
imageSrc?: string;
|
|
generatedVisualAssetId?: string;
|
|
generatedAnimationSetId?: string;
|
|
animationMap?: Record<string, unknown> | null;
|
|
updatedAt?: string;
|
|
};
|
|
|
|
export type CharacterVisualGenerationPayload = {
|
|
characterId: string;
|
|
sourceMode: Exclude<CharacterVisualSourceMode, 'upload'>;
|
|
promptText: string;
|
|
referenceImageDataUrls: string[];
|
|
candidateCount: number;
|
|
imageModel: string;
|
|
size: string;
|
|
};
|
|
|
|
export type CharacterVisualPublishPayload = {
|
|
characterId: string;
|
|
sourceMode: CharacterVisualSourceMode;
|
|
promptText: string;
|
|
selectedPreviewSource: string;
|
|
previewSources: string[];
|
|
width: number;
|
|
height: number;
|
|
updateCharacterOverride?: boolean;
|
|
};
|
|
|
|
export type CharacterAnimationGenerationPayload = {
|
|
characterId: string;
|
|
strategy: CharacterAnimationStrategy;
|
|
animation: string;
|
|
promptText: string;
|
|
characterBriefText?: string;
|
|
actionTemplateId?: string;
|
|
visualSource: string;
|
|
referenceImageDataUrls: string[];
|
|
referenceVideoDataUrls: string[];
|
|
lastFrameImageDataUrl?: string;
|
|
frameCount: number;
|
|
fps: number;
|
|
durationSeconds: number;
|
|
loop: boolean;
|
|
useChromaKey: boolean;
|
|
resolution: string;
|
|
ratio: string;
|
|
imageSequenceModel: string;
|
|
videoModel: string;
|
|
referenceVideoModel: string;
|
|
motionTransferModel: CharacterMotionTransferModel;
|
|
};
|
|
|
|
export type CharacterAnimationDraftPayload = {
|
|
framesDataUrls: string[];
|
|
fps: number;
|
|
loop: boolean;
|
|
frameWidth: number;
|
|
frameHeight: number;
|
|
frameCount?: number;
|
|
applyChromaKey?: boolean;
|
|
sampleStartRatio?: number;
|
|
sampleEndRatio?: number;
|
|
previewVideoPath?: string;
|
|
};
|
|
|
|
export type CharacterAnimationTemplate = {
|
|
id: string;
|
|
label: string;
|
|
animation: string;
|
|
promptSuffix: string;
|
|
notes: string;
|
|
};
|
|
|
|
export type CharacterAssetJobStatus = {
|
|
taskId: string;
|
|
kind: 'visual' | 'animation';
|
|
status: 'queued' | 'running' | 'completed' | 'failed';
|
|
characterId: string;
|
|
animation?: string;
|
|
strategy?: CharacterAnimationStrategy;
|
|
model: string;
|
|
prompt: string;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
result?: Record<string, unknown>;
|
|
errorMessage?: string;
|
|
};
|
|
|
|
export async function generateCharacterVisualCandidates(
|
|
payload: CharacterVisualGenerationPayload,
|
|
) {
|
|
return postApiJson<{
|
|
ok: true;
|
|
taskId: string;
|
|
model: string;
|
|
prompt: string;
|
|
drafts: CharacterVisualDraft[];
|
|
}>(CHARACTER_VISUAL_GENERATE_API_PATH, payload, '生成角色主形象失败');
|
|
}
|
|
|
|
export async function fetchCharacterWorkflowCache(
|
|
characterId: string,
|
|
cacheScopeId?: string,
|
|
) {
|
|
return fetchJson<{
|
|
ok: true;
|
|
cache: CharacterAssetWorkflowCache | null;
|
|
}>(
|
|
`${CHARACTER_WORKFLOW_CACHE_API_PATH}/${encodeURIComponent(characterId)}${
|
|
cacheScopeId
|
|
? `?cacheScopeId=${encodeURIComponent(cacheScopeId)}`
|
|
: ''
|
|
}`,
|
|
'读取角色形象生成缓存失败',
|
|
);
|
|
}
|
|
|
|
export async function saveCharacterWorkflowCache(
|
|
payload: CharacterAssetWorkflowCache,
|
|
) {
|
|
return postApiJson<{
|
|
ok: true;
|
|
cache: CharacterAssetWorkflowCache;
|
|
saveMessage: string;
|
|
}>(
|
|
CHARACTER_WORKFLOW_CACHE_API_PATH,
|
|
payload,
|
|
'保存角色形象生成缓存失败',
|
|
);
|
|
}
|
|
|
|
export async function resolveCharacterRoleAssetWorkflow(payload: {
|
|
characterId: string;
|
|
cacheScopeId?: string;
|
|
role: CharacterAssetRolePromptInput;
|
|
}) {
|
|
const { characterId, cacheScopeId, role } = payload;
|
|
return postApiJson<{
|
|
ok: true;
|
|
cache: CharacterAssetWorkflowCache | null;
|
|
workflow: CharacterRoleAssetWorkflow;
|
|
}>(
|
|
`${ROLE_ASSET_WORKFLOW_API_PATH}/${encodeURIComponent(characterId)}/workflow`,
|
|
{
|
|
cacheScopeId,
|
|
role,
|
|
},
|
|
'读取角色资产工坊工作流失败',
|
|
);
|
|
}
|
|
|
|
export async function putCharacterRoleAssetWorkflow(
|
|
payload: CharacterAssetWorkflowCache,
|
|
) {
|
|
const url = `${ROLE_ASSET_WORKFLOW_API_PATH}/${encodeURIComponent(payload.characterId)}/workflow`;
|
|
const response = await fetch(url, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload),
|
|
});
|
|
const responseText = await response.text();
|
|
|
|
if (!response.ok) {
|
|
throw new Error(
|
|
appendApiErrorRequestId(
|
|
parseApiErrorMessage(responseText, '保存角色资产工坊缓存失败'),
|
|
response.headers.get('x-request-id'),
|
|
),
|
|
);
|
|
}
|
|
|
|
return responseText
|
|
? (JSON.parse(responseText) as {
|
|
ok: true;
|
|
cache: CharacterAssetWorkflowCache;
|
|
saveMessage: string;
|
|
})
|
|
: ({
|
|
ok: true,
|
|
cache: payload,
|
|
saveMessage: '',
|
|
} as {
|
|
ok: true;
|
|
cache: CharacterAssetWorkflowCache;
|
|
saveMessage: string;
|
|
});
|
|
}
|
|
|
|
export async function fetchCharacterVisualJobStatus(taskId: string) {
|
|
return fetchJson<CharacterAssetJobStatus>(
|
|
`${CHARACTER_VISUAL_JOB_API_PATH}/${encodeURIComponent(taskId)}`,
|
|
'读取角色主形象任务状态失败',
|
|
);
|
|
}
|
|
|
|
export async function publishCharacterVisualAsset(
|
|
payload: CharacterVisualPublishPayload,
|
|
) {
|
|
return postApiJson<{
|
|
ok: true;
|
|
assetId: string;
|
|
portraitPath: string;
|
|
overrideMap: Record<string, unknown>;
|
|
saveMessage: string;
|
|
}>(CHARACTER_VISUAL_PUBLISH_API_PATH, payload, '发布角色主形象失败');
|
|
}
|
|
|
|
export async function generateCharacterAnimationDraft(
|
|
payload: CharacterAnimationGenerationPayload,
|
|
) {
|
|
return postApiJson<
|
|
| {
|
|
ok: true;
|
|
taskId: string;
|
|
strategy: 'image-sequence';
|
|
model: string;
|
|
prompt: string;
|
|
imageSources: string[];
|
|
}
|
|
| {
|
|
ok: true;
|
|
taskId: string;
|
|
strategy: 'image-to-video' | 'motion-transfer' | 'reference-to-video';
|
|
model: string;
|
|
prompt: string;
|
|
previewVideoPath: string;
|
|
}
|
|
>(CHARACTER_ANIMATION_GENERATE_API_PATH, payload, '生成角色动作草稿失败');
|
|
}
|
|
|
|
export async function fetchCharacterAnimationJobStatus(taskId: string) {
|
|
return fetchJson<CharacterAssetJobStatus>(
|
|
`${CHARACTER_ANIMATION_JOB_API_PATH}/${encodeURIComponent(taskId)}`,
|
|
'读取角色动作任务状态失败',
|
|
);
|
|
}
|
|
|
|
export async function fetchCharacterAnimationTemplates() {
|
|
return fetchJson<{
|
|
ok: true;
|
|
templates: CharacterAnimationTemplate[];
|
|
}>(CHARACTER_ANIMATION_TEMPLATES_API_PATH, '读取动作模板列表失败');
|
|
}
|
|
|
|
export async function importCharacterAnimationVideo(payload: {
|
|
characterId: string;
|
|
animation: string;
|
|
videoSource: string;
|
|
sourceLabel?: string;
|
|
}) {
|
|
return postApiJson<{
|
|
ok: true;
|
|
importedVideoPath: string;
|
|
draftId: string;
|
|
saveMessage: string;
|
|
}>(CHARACTER_ANIMATION_IMPORT_VIDEO_API_PATH, payload, '导入动作视频失败');
|
|
}
|
|
|
|
export async function publishCharacterAnimationAssets(payload: {
|
|
characterId: string;
|
|
visualAssetId: string;
|
|
animations: Record<string, CharacterAnimationDraftPayload>;
|
|
updateCharacterOverride?: boolean;
|
|
}) {
|
|
return postApiJson<{
|
|
ok: true;
|
|
animationSetId: string;
|
|
overrideMap: Record<string, unknown>;
|
|
animationMap: Record<string, unknown>;
|
|
saveMessage: string;
|
|
}>(CHARACTER_ANIMATION_PUBLISH_API_PATH, payload, '发布角色基础动作失败');
|
|
}
|