fix: preserve rpg custom world detail profiles
This commit is contained in:
157
src/components/rpg-entry/rpgProfileCompleteness.ts
Normal file
157
src/components/rpg-entry/rpgProfileCompleteness.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user