1
This commit is contained in:
@@ -1,3 +1,7 @@
|
||||
import type {
|
||||
EightAnchorContent,
|
||||
KeyRelationshipValue,
|
||||
} from '../../packages/shared/src/contracts/customWorldAgent';
|
||||
import {
|
||||
type ReactNode,
|
||||
useDeferredValue,
|
||||
@@ -13,10 +17,7 @@ import {
|
||||
resolveCustomWorldLandmarkImageMap,
|
||||
} from '../data/customWorldVisuals';
|
||||
import { resolveCustomWorldCampScene } from '../services/customWorldCamp';
|
||||
import {
|
||||
buildCustomWorldCreatorIntentFoundationText,
|
||||
normalizeCustomWorldCreatorIntent,
|
||||
} from '../services/customWorldCreatorIntent';
|
||||
import { normalizeCustomWorldCreatorIntent } from '../services/customWorldCreatorIntent';
|
||||
import { AnimationState, Character, CustomWorldProfile } from '../types';
|
||||
import { getNineSliceStyle, UI_CHROME } from '../uiAssets';
|
||||
import { CharacterAnimator } from './CharacterAnimator';
|
||||
@@ -348,6 +349,226 @@ function compactTextList(values: Array<string | null | undefined>) {
|
||||
return values.map((value) => value?.trim() ?? '').filter(Boolean);
|
||||
}
|
||||
|
||||
function toText(value: unknown) {
|
||||
return typeof value === 'string' ? value.trim() : '';
|
||||
}
|
||||
|
||||
function toTextArray(value: unknown) {
|
||||
return Array.isArray(value)
|
||||
? value.map((item) => toText(item)).filter(Boolean)
|
||||
: [];
|
||||
}
|
||||
|
||||
function toRecord(value: unknown) {
|
||||
return value && typeof value === 'object' && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: null;
|
||||
}
|
||||
|
||||
function buildRelationshipSeedText(value: unknown) {
|
||||
const record = toRecord(value);
|
||||
if (!record) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return compactTextList([
|
||||
toText(record.name),
|
||||
toText(record.role),
|
||||
toText(record.relationToPlayer)
|
||||
? `与玩家:${toText(record.relationToPlayer)}`
|
||||
: '',
|
||||
toText(record.hiddenHook) ? `代价/暗线:${toText(record.hiddenHook)}` : '',
|
||||
]).join(';');
|
||||
}
|
||||
|
||||
function buildKeyRelationshipText(value: KeyRelationshipValue) {
|
||||
return compactTextList([
|
||||
value.pairs,
|
||||
value.relationshipType,
|
||||
value.secretOrCost ? `代价/秘密:${value.secretOrCost}` : '',
|
||||
]).join(';');
|
||||
}
|
||||
|
||||
function buildAnchorContentFromProfileFallback(
|
||||
profile: CustomWorldProfile,
|
||||
): EightAnchorContent {
|
||||
const creatorIntent = normalizeCustomWorldCreatorIntent(profile.creatorIntent);
|
||||
const relationshipSeed = creatorIntent?.keyCharacters[0] ?? null;
|
||||
|
||||
return {
|
||||
worldPromise: {
|
||||
hook:
|
||||
creatorIntent?.worldHook ||
|
||||
profile.anchorPack?.worldSummary ||
|
||||
profile.summary,
|
||||
differentiator: profile.subtitle || profile.settingText,
|
||||
desiredExperience:
|
||||
compactTextList([
|
||||
creatorIntent?.toneDirectives.join('、') || '',
|
||||
profile.tone,
|
||||
]).join(';') || profile.tone,
|
||||
},
|
||||
playerFantasy: {
|
||||
playerRole: creatorIntent?.playerPremise || profile.playerGoal,
|
||||
corePursuit: profile.playerGoal,
|
||||
fearOfLoss:
|
||||
relationshipSeed?.hiddenHook ||
|
||||
creatorIntent?.coreConflicts[0] ||
|
||||
profile.coreConflicts[0] ||
|
||||
'',
|
||||
},
|
||||
themeBoundary: {
|
||||
toneKeywords: compactTextList([
|
||||
creatorIntent?.themeKeywords.join('、') || '',
|
||||
creatorIntent?.toneDirectives.join('、') || '',
|
||||
]),
|
||||
aestheticDirectives: compactTextList([profile.tone, profile.subtitle]),
|
||||
forbiddenDirectives: creatorIntent?.forbiddenDirectives ?? [],
|
||||
},
|
||||
playerEntryPoint: {
|
||||
openingIdentity: creatorIntent?.playerPremise || '',
|
||||
openingProblem:
|
||||
creatorIntent?.openingSituation || profile.coreConflicts[0] || '',
|
||||
entryMotivation: profile.playerGoal,
|
||||
},
|
||||
coreConflict: {
|
||||
surfaceConflicts:
|
||||
creatorIntent?.coreConflicts.length
|
||||
? creatorIntent.coreConflicts
|
||||
: profile.coreConflicts,
|
||||
hiddenCrisis:
|
||||
relationshipSeed?.hiddenHook ||
|
||||
profile.summary ||
|
||||
profile.settingText,
|
||||
firstTouchedConflict:
|
||||
creatorIntent?.openingSituation ||
|
||||
profile.coreConflicts[0] ||
|
||||
profile.playerGoal,
|
||||
},
|
||||
keyRelationships: relationshipSeed
|
||||
? [
|
||||
{
|
||||
pairs: compactTextList([
|
||||
relationshipSeed.name,
|
||||
relationshipSeed.role,
|
||||
]).join(' · '),
|
||||
relationshipType: relationshipSeed.relationToPlayer || '',
|
||||
secretOrCost: relationshipSeed.hiddenHook || '',
|
||||
},
|
||||
]
|
||||
: [],
|
||||
hiddenLines: {
|
||||
hiddenTruths: compactTextList([
|
||||
relationshipSeed?.hiddenHook || '',
|
||||
profile.summary,
|
||||
]),
|
||||
misdirectionHints: compactTextList([
|
||||
profile.subtitle,
|
||||
profile.majorFactions[0] || '',
|
||||
]),
|
||||
revealPacing:
|
||||
creatorIntent?.openingSituation ||
|
||||
profile.coreConflicts[0] ||
|
||||
profile.playerGoal,
|
||||
},
|
||||
iconicElements: {
|
||||
iconicMotifs:
|
||||
creatorIntent?.iconicElements.length
|
||||
? creatorIntent.iconicElements
|
||||
: compactTextList([
|
||||
profile.anchorPack?.motifDirectives.join('、') || '',
|
||||
profile.landmarks[0]?.name || '',
|
||||
]),
|
||||
institutionsOrArtifacts: compactTextList([
|
||||
profile.camp?.name || '',
|
||||
profile.majorFactions[0] || '',
|
||||
]),
|
||||
hardRules: compactTextList([profile.playerGoal, profile.coreConflicts[0] || '']),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getProfileAnchorContent(profile: CustomWorldProfile) {
|
||||
const anchorContentRecord = profile.anchorContent;
|
||||
if (!anchorContentRecord) {
|
||||
return buildAnchorContentFromProfileFallback(profile);
|
||||
}
|
||||
|
||||
const worldPromiseRecord = toRecord(anchorContentRecord.worldPromise);
|
||||
const playerFantasyRecord = toRecord(anchorContentRecord.playerFantasy);
|
||||
const themeBoundaryRecord = toRecord(anchorContentRecord.themeBoundary);
|
||||
const playerEntryPointRecord = toRecord(anchorContentRecord.playerEntryPoint);
|
||||
const coreConflictRecord = toRecord(anchorContentRecord.coreConflict);
|
||||
const hiddenLinesRecord = toRecord(anchorContentRecord.hiddenLines);
|
||||
const iconicElementsRecord = toRecord(anchorContentRecord.iconicElements);
|
||||
|
||||
return {
|
||||
worldPromise: worldPromiseRecord
|
||||
? {
|
||||
hook: toText(worldPromiseRecord.hook),
|
||||
differentiator: toText(worldPromiseRecord.differentiator),
|
||||
desiredExperience: toText(worldPromiseRecord.desiredExperience),
|
||||
}
|
||||
: null,
|
||||
playerFantasy: playerFantasyRecord
|
||||
? {
|
||||
playerRole: toText(playerFantasyRecord.playerRole),
|
||||
corePursuit: toText(playerFantasyRecord.corePursuit),
|
||||
fearOfLoss: toText(playerFantasyRecord.fearOfLoss),
|
||||
}
|
||||
: null,
|
||||
themeBoundary: themeBoundaryRecord
|
||||
? {
|
||||
toneKeywords: toTextArray(themeBoundaryRecord.toneKeywords),
|
||||
aestheticDirectives: toTextArray(
|
||||
themeBoundaryRecord.aestheticDirectives,
|
||||
),
|
||||
forbiddenDirectives: toTextArray(themeBoundaryRecord.forbiddenDirectives),
|
||||
}
|
||||
: null,
|
||||
playerEntryPoint: playerEntryPointRecord
|
||||
? {
|
||||
openingIdentity: toText(playerEntryPointRecord.openingIdentity),
|
||||
openingProblem: toText(playerEntryPointRecord.openingProblem),
|
||||
entryMotivation: toText(playerEntryPointRecord.entryMotivation),
|
||||
}
|
||||
: null,
|
||||
coreConflict: coreConflictRecord
|
||||
? {
|
||||
surfaceConflicts: toTextArray(coreConflictRecord.surfaceConflicts),
|
||||
hiddenCrisis: toText(coreConflictRecord.hiddenCrisis),
|
||||
firstTouchedConflict: toText(coreConflictRecord.firstTouchedConflict),
|
||||
}
|
||||
: null,
|
||||
keyRelationships: Array.isArray(anchorContentRecord.keyRelationships)
|
||||
? anchorContentRecord.keyRelationships
|
||||
.map((entry) => toRecord(entry))
|
||||
.filter(Boolean)
|
||||
.map((entry) => ({
|
||||
pairs: toText(entry?.pairs),
|
||||
relationshipType: toText(entry?.relationshipType),
|
||||
secretOrCost: toText(entry?.secretOrCost),
|
||||
}))
|
||||
: [],
|
||||
hiddenLines: hiddenLinesRecord
|
||||
? {
|
||||
hiddenTruths: toTextArray(hiddenLinesRecord.hiddenTruths),
|
||||
misdirectionHints: toTextArray(hiddenLinesRecord.misdirectionHints),
|
||||
revealPacing: toText(hiddenLinesRecord.revealPacing),
|
||||
}
|
||||
: null,
|
||||
iconicElements: iconicElementsRecord
|
||||
? {
|
||||
iconicMotifs: toTextArray(iconicElementsRecord.iconicMotifs),
|
||||
institutionsOrArtifacts: toTextArray(
|
||||
iconicElementsRecord.institutionsOrArtifacts,
|
||||
),
|
||||
hardRules: toTextArray(iconicElementsRecord.hardRules),
|
||||
}
|
||||
: null,
|
||||
} satisfies EightAnchorContent;
|
||||
}
|
||||
|
||||
function buildOpeningSceneSearchText(
|
||||
profile: CustomWorldProfile,
|
||||
campScene: ReturnType<typeof resolveCustomWorldCampScene>,
|
||||
@@ -365,71 +586,85 @@ function buildOpeningSceneSearchText(
|
||||
|
||||
function buildStructuredFoundationEntries(profile: CustomWorldProfile) {
|
||||
const creatorIntent = normalizeCustomWorldCreatorIntent(profile.creatorIntent);
|
||||
const relationshipSeed = creatorIntent?.keyCharacters[0];
|
||||
const relationshipText = relationshipSeed
|
||||
? compactTextList([
|
||||
relationshipSeed.name,
|
||||
relationshipSeed.role,
|
||||
relationshipSeed.relationToPlayer
|
||||
? `与玩家:${relationshipSeed.relationToPlayer}`
|
||||
: '',
|
||||
relationshipSeed.hiddenHook
|
||||
? `暗线:${relationshipSeed.hiddenHook}`
|
||||
: '',
|
||||
]).join(' · ')
|
||||
: '';
|
||||
const themeToneText = compactTextList([
|
||||
creatorIntent?.themeKeywords.join('、') || '',
|
||||
creatorIntent?.toneDirectives.join('、') || '',
|
||||
]).join(' / ');
|
||||
const playerOpeningText = compactTextList([
|
||||
creatorIntent?.playerPremise || '',
|
||||
creatorIntent?.openingSituation || '',
|
||||
]).join(';');
|
||||
const anchorContent = getProfileAnchorContent(profile);
|
||||
const fallbackRelationshipText =
|
||||
buildRelationshipSeedText(creatorIntent?.keyCharacters[0]) ||
|
||||
profile.playableNpcs[0]?.relationshipHooks.join(';') ||
|
||||
profile.storyNpcs[0]?.relationshipHooks.join(';') ||
|
||||
'';
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'world-hook',
|
||||
label: '世界一句话',
|
||||
value:
|
||||
creatorIntent?.worldHook ||
|
||||
profile.anchorPack?.worldSummary ||
|
||||
profile.summary,
|
||||
id: 'world-promise',
|
||||
label: '世界承诺',
|
||||
value: compactTextList([
|
||||
anchorContent.worldPromise?.hook || '',
|
||||
anchorContent.worldPromise?.differentiator || '',
|
||||
anchorContent.worldPromise?.desiredExperience || '',
|
||||
]).join(';'),
|
||||
},
|
||||
{
|
||||
id: 'player-opening',
|
||||
label: '玩家开局',
|
||||
value: playerOpeningText || profile.playerGoal,
|
||||
id: 'player-fantasy',
|
||||
label: '玩家幻想',
|
||||
value: compactTextList([
|
||||
anchorContent.playerFantasy?.playerRole || '',
|
||||
anchorContent.playerFantasy?.corePursuit || '',
|
||||
anchorContent.playerFantasy?.fearOfLoss || '',
|
||||
]).join(';'),
|
||||
},
|
||||
{
|
||||
id: 'theme-tone',
|
||||
label: '主题气质',
|
||||
value: themeToneText || profile.tone,
|
||||
id: 'theme-boundary',
|
||||
label: '主题边界',
|
||||
value: compactTextList([
|
||||
anchorContent.themeBoundary?.toneKeywords.join('、') || '',
|
||||
anchorContent.themeBoundary?.aestheticDirectives.join('、') || '',
|
||||
anchorContent.themeBoundary?.forbiddenDirectives.length
|
||||
? `避免:${anchorContent.themeBoundary.forbiddenDirectives.join('、')}`
|
||||
: '',
|
||||
]).join(';'),
|
||||
},
|
||||
{
|
||||
id: 'player-entry-point',
|
||||
label: '玩家切入口',
|
||||
value: compactTextList([
|
||||
anchorContent.playerEntryPoint?.openingIdentity || '',
|
||||
anchorContent.playerEntryPoint?.openingProblem || '',
|
||||
anchorContent.playerEntryPoint?.entryMotivation || '',
|
||||
]).join(';'),
|
||||
},
|
||||
{
|
||||
id: 'core-conflict',
|
||||
label: '核心冲突',
|
||||
value:
|
||||
creatorIntent?.coreConflicts.join(';') ||
|
||||
profile.coreConflicts.join(';') ||
|
||||
profile.summary,
|
||||
value: compactTextList([
|
||||
anchorContent.coreConflict?.surfaceConflicts.join('、') || '',
|
||||
anchorContent.coreConflict?.hiddenCrisis || '',
|
||||
anchorContent.coreConflict?.firstTouchedConflict || '',
|
||||
]).join(';'),
|
||||
},
|
||||
{
|
||||
id: 'relationship-seed',
|
||||
id: 'key-relationships',
|
||||
label: '关键关系',
|
||||
value:
|
||||
relationshipText ||
|
||||
profile.playableNpcs[0]?.relationshipHooks.join(';') ||
|
||||
profile.storyNpcs[0]?.relationshipHooks.join(';') ||
|
||||
'待补充',
|
||||
anchorContent.keyRelationships.map(buildKeyRelationshipText).join('\n') ||
|
||||
fallbackRelationshipText,
|
||||
},
|
||||
{
|
||||
id: 'hidden-lines',
|
||||
label: '暗线与揭示',
|
||||
value: compactTextList([
|
||||
anchorContent.hiddenLines?.hiddenTruths.join('、') || '',
|
||||
anchorContent.hiddenLines?.misdirectionHints.join('、') || '',
|
||||
anchorContent.hiddenLines?.revealPacing || '',
|
||||
]).join(';'),
|
||||
},
|
||||
{
|
||||
id: 'iconic-elements',
|
||||
label: '标志元素',
|
||||
value:
|
||||
creatorIntent?.iconicElements.join('、') ||
|
||||
profile.anchorPack?.motifDirectives.join('、') ||
|
||||
'待补充',
|
||||
value: compactTextList([
|
||||
anchorContent.iconicElements?.iconicMotifs.join('、') || '',
|
||||
anchorContent.iconicElements?.institutionsOrArtifacts.join('、') || '',
|
||||
anchorContent.iconicElements?.hardRules.join('、') || '',
|
||||
]).join(';'),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -594,12 +829,6 @@ export function CustomWorldEntityCatalog({
|
||||
() => buildStructuredFoundationEntries(profile),
|
||||
[profile],
|
||||
);
|
||||
const structuredFoundationSourceText = useMemo(
|
||||
() =>
|
||||
buildCustomWorldCreatorIntentFoundationText(profile.creatorIntent).trim() ||
|
||||
profile.settingText.trim(),
|
||||
[profile.creatorIntent, profile.settingText],
|
||||
);
|
||||
const normalizedCreatorIntent = useMemo(
|
||||
() => normalizeCustomWorldCreatorIntent(profile.creatorIntent),
|
||||
[profile.creatorIntent],
|
||||
@@ -907,9 +1136,6 @@ export function CustomWorldEntityCatalog({
|
||||
}
|
||||
>
|
||||
<div className="space-y-3">
|
||||
<div className="text-[11px] font-bold tracking-[0.18em] text-zinc-500">
|
||||
解析字段
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
{structuredFoundationEntries.map((entry) => (
|
||||
<div
|
||||
@@ -919,22 +1145,12 @@ export function CustomWorldEntityCatalog({
|
||||
<div className="text-[11px] font-bold tracking-[0.18em] text-zinc-500">
|
||||
{entry.label}
|
||||
</div>
|
||||
<div className="mt-2 text-sm leading-7 text-zinc-100">
|
||||
<div className="mt-2 whitespace-pre-line text-sm leading-7 text-zinc-100">
|
||||
{entry.value || '待补充'}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{structuredFoundationSourceText ? (
|
||||
<div className="rounded-2xl border border-white/8 bg-black/20 px-4 py-4">
|
||||
<div className="text-[11px] font-bold tracking-[0.18em] text-zinc-500">
|
||||
锚点原文
|
||||
</div>
|
||||
<div className="mt-2 whitespace-pre-line text-sm leading-7 text-zinc-200">
|
||||
{structuredFoundationSourceText}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</Section>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user