Files
Genarrative/src/services/customWorldCover.ts
2026-04-25 13:44:48 +08:00

133 lines
3.6 KiB
TypeScript

import type {
CustomWorldCoverProfile,
CustomWorldPlayableNpc,
CustomWorldProfile,
} from '../types';
export type CustomWorldCoverRenderMode = 'image' | 'scene_with_roles';
export type CustomWorldCoverPresentation = {
imageSrc: string | null;
renderMode: CustomWorldCoverRenderMode;
characterImageSrcs: string[];
sourceType: CustomWorldCoverProfile['sourceType'];
};
function resolveOpeningSceneFirstActImageSrc(profile: CustomWorldProfile) {
return profile.sceneChapterBlueprints?.[0]?.acts?.[0]?.backgroundImageSrc?.trim() || null;
}
function resolveOpeningSceneImageSrc(profile: CustomWorldProfile) {
// 默认封面优先取开局场景第一幕图,避免草稿页与作品库继续沿用旧的营地兜底策略。
const firstActImageSrc = resolveOpeningSceneFirstActImageSrc(profile);
if (firstActImageSrc) {
return firstActImageSrc;
}
const campImageSrc = profile.camp?.imageSrc?.trim() || '';
if (campImageSrc) {
return campImageSrc;
}
return (
profile.landmarks
.map((landmark) => landmark.imageSrc?.trim() || '')
.find(Boolean) || null
);
}
function resolvePlayableCoverImageSrc(role: CustomWorldPlayableNpc) {
const explicitImageSrc = role.imageSrc?.trim() || '';
if (explicitImageSrc) {
return explicitImageSrc;
}
return null;
}
function normalizeCoverCharacterRoleIds(
profile: Pick<CustomWorldProfile, 'playableNpcs'>,
roleIds?: string[] | null,
) {
const availableIds = new Set(
profile.playableNpcs.map((role) => role.id.trim()).filter(Boolean),
);
const selectedIds = Array.isArray(roleIds)
? [
...new Set(
roleIds
.map((roleId) => roleId.trim())
.filter((roleId) => roleId && availableIds.has(roleId)),
),
].slice(0, 3)
: [];
if (selectedIds.length > 0) {
return selectedIds;
}
return profile.playableNpcs
.map((role) => role.id.trim())
.filter(Boolean)
.slice(0, 3);
}
export function buildDefaultCustomWorldCoverProfile(
profile: Pick<CustomWorldProfile, 'playableNpcs'>,
): CustomWorldCoverProfile {
return {
sourceType: 'default',
imageSrc: null,
characterRoleIds: normalizeCoverCharacterRoleIds(profile),
};
}
export function resolveCustomWorldCoverPresentation(
profile: CustomWorldProfile,
): CustomWorldCoverPresentation {
const cover = profile.cover;
const sourceType =
cover?.sourceType === 'uploaded' || cover?.sourceType === 'generated'
? cover.sourceType
: 'default';
const explicitImageSrc = cover?.imageSrc?.trim() || '';
if (sourceType !== 'default' && explicitImageSrc) {
return {
imageSrc: explicitImageSrc,
renderMode: 'image',
characterImageSrcs: [],
sourceType,
};
}
const openingSceneImageSrc = resolveOpeningSceneImageSrc(profile);
const roleById = new Map(
profile.playableNpcs.map((role) => [role.id.trim(), role] as const),
);
const characterImageSrcs = normalizeCoverCharacterRoleIds(
profile,
cover?.characterRoleIds,
)
.map((roleId) => roleById.get(roleId))
.map((role) => (role ? resolvePlayableCoverImageSrc(role) : null))
.filter((imageSrc): imageSrc is string => Boolean(imageSrc));
const leadPlayableImageSrc =
profile.playableNpcs
.map((role) => resolvePlayableCoverImageSrc(role))
.find(Boolean) || null;
return {
imageSrc: openingSceneImageSrc || leadPlayableImageSrc,
renderMode:
openingSceneImageSrc && characterImageSrcs.length > 0
? 'scene_with_roles'
: 'image',
characterImageSrcs:
openingSceneImageSrc && characterImageSrcs.length > 0
? characterImageSrcs
: [],
sourceType: 'default',
};
}