This commit is contained in:
2026-04-25 13:44:48 +08:00
parent 03acbc5cb1
commit 2ebb7bf253
44 changed files with 1003 additions and 250 deletions

View File

@@ -3473,13 +3473,7 @@ export function SectionPanel({
function buildRolePreviewCharacter(
role: CustomWorldPlayableNpc | CustomWorldNpc,
): Character | null {
const template =
'templateCharacterId' in role && role.templateCharacterId
? ROLE_TEMPLATE_CHARACTERS.find(
(entry) => entry.id === role.templateCharacterId,
) ?? null
: null;
const portrait = role.imageSrc || template?.portrait;
const portrait = role.imageSrc;
if (!portrait) {
return null;
@@ -3493,15 +3487,15 @@ function buildRolePreviewCharacter(
backstory: role.backstory,
avatar: portrait,
portrait,
assetFolder: template?.assetFolder ?? 'custom-world',
assetVariant: template?.assetVariant ?? 'generated',
assetFolder: 'custom-world',
assetVariant: 'generated',
generatedVisualAssetId: role.generatedVisualAssetId,
generatedAnimationSetId: role.generatedAnimationSetId,
animationMap: template?.animationMap ?? role.animationMap,
attributes: template?.attributes ?? {},
animationMap: role.animationMap,
attributes: { strength: 0, agility: 0, intelligence: 0, spirit: 0 },
personality: role.personality,
skills: template?.skills ?? [],
adventureOpenings: template?.adventureOpenings ?? {},
skills: [],
adventureOpenings: {},
} as Character;
}
@@ -4568,12 +4562,7 @@ export function PlayableNpcEditor({
const initialSnapshot = useMemo(() => JSON.stringify(npc), [npc]);
const draftSnapshot = useMemo(() => JSON.stringify(draft), [draft]);
const hasUnsavedChanges = draftSnapshot !== initialSnapshot;
const selectedTemplate =
ROLE_TEMPLATE_CHARACTERS.find(
(character) => character.id === draft.templateCharacterId,
) ??
ROLE_TEMPLATE_CHARACTERS[0] ??
null;
const previewImageSrc = draft.imageSrc?.trim() ?? '';
const roleOptions = useMemo(
() =>
[...profile.playableNpcs, ...profile.storyNpcs]
@@ -4608,7 +4597,7 @@ export function PlayableNpcEditor({
disableClose={isAiAssetStudioOpen || isCloseConfirmOpen}
>
<div className="space-y-4">
{selectedTemplate ? (
{previewImageSrc ? (
<div className="rounded-2xl border border-white/8 bg-black/20 p-4">
<div className="text-[11px] font-bold tracking-[0.18em] text-zinc-300">
@@ -4616,20 +4605,17 @@ export function PlayableNpcEditor({
<div className="mt-3 grid gap-4 sm:grid-cols-[7rem_minmax(0,1fr)]">
<div className="overflow-hidden rounded-2xl border border-white/10 bg-black/30">
<img
src={draft.imageSrc || selectedTemplate.portrait}
alt={selectedTemplate.name}
src={previewImageSrc}
alt={draft.name || '角色形象'}
className="h-28 w-full object-cover object-top"
/>
</div>
<div className="min-w-0">
<div className="text-base font-semibold text-white">
{selectedTemplate.name}
{draft.name || '未命名角色'}
</div>
<div className="mt-1 text-sm text-zinc-400">
{selectedTemplate.title}
</div>
<div className="mt-3 text-sm leading-6 text-zinc-300">
{selectedTemplate.description}
{draft.title || draft.role}
</div>
<div className="mt-3 flex flex-wrap gap-2">
{draft.generatedVisualAssetId ? (
@@ -4654,22 +4640,6 @@ export function PlayableNpcEditor({
</div>
</div>
) : null}
<Field label="外观模板">
<SelectField
value={draft.templateCharacterId ?? ROLE_TEMPLATE_CHARACTERS[0]?.id ?? ''}
onChange={(value) =>
setDraft((current) => ({
...current,
templateCharacterId: value,
}))
}
options={ROLE_TEMPLATE_CHARACTERS.map((character) => ({
value: character.id,
label: `${character.name} / ${character.title}`,
}))}
/>
</Field>
<Field label="名称">
<TextInput
value={draft.name}
@@ -4798,11 +4768,7 @@ export function PlayableNpcEditor({
<SaveBar
onClose={handleRequestClose}
onSave={() => {
onSave({
...draft,
templateCharacterId:
draft.templateCharacterId ?? ROLE_TEMPLATE_CHARACTERS[0]?.id,
});
onSave(draft);
onClose();
}}
showClose={false}