158 lines
4.9 KiB
TypeScript
158 lines
4.9 KiB
TypeScript
import type { CustomWorldProfile } from '../../types';
|
||
|
||
export function countCustomWorldProfileDetailSlots(
|
||
profile: Partial<CustomWorldProfile> | null | undefined,
|
||
) {
|
||
if (!profile) {
|
||
return 0;
|
||
}
|
||
|
||
return (
|
||
(profile.playableNpcs?.length ?? 0) +
|
||
(profile.storyNpcs?.length ?? 0) +
|
||
(profile.items?.length ?? 0) +
|
||
(profile.landmarks?.length ?? 0) +
|
||
(profile.sceneChapterBlueprints?.length ?? 0)
|
||
);
|
||
}
|
||
|
||
export function countCustomWorldProfileAssetSlots(
|
||
profile: Partial<CustomWorldProfile> | null | undefined,
|
||
) {
|
||
if (!profile) {
|
||
return 0;
|
||
}
|
||
|
||
return [
|
||
profile.cover?.imageSrc,
|
||
profile.openingCg?.storyboardImageSrc,
|
||
profile.openingCg?.videoSrc,
|
||
profile.openingCg?.posterImageSrc,
|
||
profile.camp?.imageSrc,
|
||
...(profile.playableNpcs ?? []).flatMap((role) => [
|
||
role.imageSrc,
|
||
role.generatedVisualAssetId,
|
||
role.generatedAnimationSetId,
|
||
...(role.skills ?? []).flatMap((skill) => [
|
||
skill.actionPreviewConfig?.basePath,
|
||
skill.actionPreviewConfig?.previewVideoPath,
|
||
skill.actionPreviewConfig?.file,
|
||
]),
|
||
...role.initialItems.flatMap((item) => [item.iconSrc]),
|
||
...Object.values(role.animationMap ?? {}).flatMap((config) => [
|
||
config?.basePath,
|
||
config?.previewVideoPath,
|
||
config?.file,
|
||
]),
|
||
]),
|
||
...(profile.storyNpcs ?? []).flatMap((npc) => [
|
||
npc.imageSrc,
|
||
npc.generatedVisualAssetId,
|
||
npc.generatedAnimationSetId,
|
||
...(npc.skills ?? []).flatMap((skill) => [
|
||
skill.actionPreviewConfig?.basePath,
|
||
skill.actionPreviewConfig?.previewVideoPath,
|
||
skill.actionPreviewConfig?.file,
|
||
]),
|
||
...npc.initialItems.flatMap((item) => [item.iconSrc]),
|
||
...Object.values(npc.animationMap ?? {}).flatMap((config) => [
|
||
config?.basePath,
|
||
config?.previewVideoPath,
|
||
config?.file,
|
||
]),
|
||
]),
|
||
...(profile.items ?? []).flatMap((item) => [item.iconSrc, item.sourcePath]),
|
||
...(profile.landmarks ?? []).map((landmark) => landmark.imageSrc),
|
||
...(profile.sceneChapterBlueprints ?? []).flatMap((chapter) =>
|
||
chapter.acts.flatMap((act) => [
|
||
act.backgroundImageSrc,
|
||
act.backgroundAssetId,
|
||
]),
|
||
),
|
||
].filter((value) => value?.trim()).length;
|
||
}
|
||
|
||
export function countCustomWorldProfileStructuredSlots(
|
||
profile: Partial<CustomWorldProfile> | null | undefined,
|
||
) {
|
||
if (!profile) {
|
||
return 0;
|
||
}
|
||
|
||
return [
|
||
profile.cover,
|
||
profile.attributeSchema,
|
||
profile.themePack,
|
||
profile.storyGraph,
|
||
profile.knowledgeFacts?.length ? profile.knowledgeFacts : null,
|
||
profile.threadContracts?.length ? profile.threadContracts : null,
|
||
profile.anchorContent,
|
||
profile.creatorIntent,
|
||
profile.anchorPack,
|
||
profile.lockState,
|
||
profile.ownedSettingLayers,
|
||
profile.generationMode,
|
||
profile.generationStatus,
|
||
profile.scenarioPackId,
|
||
profile.campaignPackId,
|
||
...(profile.playableNpcs ?? []).flatMap((role) => [
|
||
role.attributeProfile,
|
||
...(role.skills ?? []).map((skill) => skill.actionPreviewConfig),
|
||
...role.initialItems,
|
||
]),
|
||
...(profile.storyNpcs ?? []).flatMap((npc) => [
|
||
npc.attributeProfile,
|
||
...(npc.skills ?? []).map((skill) => skill.actionPreviewConfig),
|
||
...npc.initialItems,
|
||
]),
|
||
...(profile.landmarks ?? []).flatMap((landmark) => [
|
||
landmark.visualDescription,
|
||
landmark.narrativeResidues?.length ? landmark.narrativeResidues : null,
|
||
]),
|
||
...((profile.camp?.narrativeResidues ?? []).length
|
||
? [profile.camp?.narrativeResidues]
|
||
: []),
|
||
...(profile.sceneChapterBlueprints ?? []).flatMap((chapter) => [
|
||
chapter.sceneTaskDescription,
|
||
chapter.linkedThreadIds.length ? chapter.linkedThreadIds : null,
|
||
chapter.linkedLandmarkIds.length ? chapter.linkedLandmarkIds : null,
|
||
...chapter.acts.flatMap((act) => [
|
||
act.eventDescription,
|
||
act.linkedThreadIds.length ? act.linkedThreadIds : null,
|
||
act.actGoal,
|
||
act.transitionHook,
|
||
]),
|
||
]),
|
||
].filter(Boolean).length;
|
||
}
|
||
|
||
export function getCustomWorldProfileCompletenessScore(
|
||
profile: Partial<CustomWorldProfile> | null | undefined,
|
||
) {
|
||
return (
|
||
countCustomWorldProfileDetailSlots(profile) +
|
||
countCustomWorldProfileAssetSlots(profile) +
|
||
countCustomWorldProfileStructuredSlots(profile)
|
||
);
|
||
}
|
||
|
||
export function chooseMoreCompleteCustomWorldProfile(
|
||
fallbackProfile: CustomWorldProfile,
|
||
candidateProfile: CustomWorldProfile | null | undefined,
|
||
) {
|
||
if (!candidateProfile) {
|
||
return fallbackProfile;
|
||
}
|
||
|
||
if (candidateProfile.id !== fallbackProfile.id) {
|
||
return candidateProfile;
|
||
}
|
||
|
||
// 中文注释:发布 / 回读可能只返回列表摘要或旧快照。
|
||
// 同一个 profileId 下,进入世界不能把当前结果页的封面、CG、角色资产降级掉。
|
||
return getCustomWorldProfileCompletenessScore(candidateProfile) >=
|
||
getCustomWorldProfileCompletenessScore(fallbackProfile)
|
||
? candidateProfile
|
||
: fallbackProfile;
|
||
}
|