Persist custom world asset configs in runtime snapshots

This commit is contained in:
2026-04-18 17:00:46 +08:00
parent 7ce61e9879
commit ac801fe05f
29 changed files with 3397 additions and 400 deletions

View File

@@ -3,8 +3,8 @@ import {
RefreshCcw,
} from 'lucide-react';
import {
type CSSProperties,
type ChangeEvent,
type CSSProperties,
type ReactNode,
useEffect,
useMemo,
@@ -15,8 +15,8 @@ import { createPortal } from 'react-dom';
import { ROLE_TEMPLATE_CHARACTERS } from '../data/characterPresets';
import {
AnimationState,
type CharacterAnimationConfig,
type Character,
type CharacterAnimationConfig,
} from '../types';
import {
buildAnimationClipFromVideoSource,
@@ -358,6 +358,37 @@ function buildRoleCharacterBrief(
.join('\n');
}
function isLegacyGeneratedVisualDescription(value: string) {
const normalized = value.trim();
if (!normalized) {
return false;
}
return [
'2D 横版 RPG',
'纯绿色绿幕',
'2 到 2.5 头身',
'深色粗轮廓',
'身体整体朝右',
'脚底完整可见',
].some((marker) => normalized.includes(marker));
}
function isLegacyGeneratedActionDescription(value: string) {
const normalized = value.trim();
if (!normalized) {
return false;
}
return [
'动作气质参考:',
'发力起手明确',
'收招利落',
'动作表现偏向',
'起手克制',
].some((marker) => normalized.includes(marker));
}
function mergeRole<T extends EditableCustomWorldRole>(
role: T,
patch: Partial<T>,
@@ -712,10 +743,16 @@ export function CustomWorldRoleAssetStudioModal({
});
setWorkingRole(nextRole);
setVisualPromptText(
cache.visualPromptText || initialPromptBundle.visualPromptText,
cache.visualPromptText &&
!isLegacyGeneratedVisualDescription(cache.visualPromptText)
? cache.visualPromptText
: initialPromptBundle.visualPromptText,
);
setAnimationPromptText(
cache.animationPromptText || initialPromptBundle.animationPromptText,
cache.animationPromptText &&
!isLegacyGeneratedActionDescription(cache.animationPromptText)
? cache.animationPromptText
: initialPromptBundle.animationPromptText,
);
setVisualDrafts(cache.visualDrafts ?? []);
setSelectedVisualDraftId(
@@ -904,6 +941,8 @@ export function CustomWorldRoleAssetStudioModal({
}
const isLoopAction = config.loop;
const shouldUseLastFrameReference =
!isLoopAction && config.animation !== AnimationState.DIE;
const result = await generateCharacterAnimationDraft({
characterId: workingRole.id,
@@ -915,7 +954,9 @@ export function CustomWorldRoleAssetStudioModal({
visualSource: workingRole.imageSrc,
referenceImageDataUrls: [],
referenceVideoDataUrls: [],
lastFrameImageDataUrl: isLoopAction ? undefined : workingRole.imageSrc,
lastFrameImageDataUrl: shouldUseLastFrameReference
? workingRole.imageSrc
: undefined,
frameCount: config.frameCount,
fps: config.fps,
durationSeconds: config.durationSeconds,
@@ -1108,12 +1149,12 @@ export function CustomWorldRoleAssetStudioModal({
</div>
</div>
<Field label="形象提示词">
<Field label="形象描述">
<TextArea
value={visualPromptText}
onChange={setVisualPromptText}
rows={6}
placeholder="角色形象提示词会先按设定自动生成,也可以继续手动细化。"
placeholder="这里默认展示角色形象描述,也可以继续手动细化。"
/>
</Field>
@@ -1296,12 +1337,12 @@ export function CustomWorldRoleAssetStudioModal({
})}
</div>
<Field label="动作提示词">
<Field label="动作描述">
<TextArea
value={animationPromptText}
onChange={setAnimationPromptText}
rows={5}
placeholder="角色动作提示词会先按设定自动生成,也可以继续手动细化。"
placeholder="这里默认展示角色动作描述,也可以继续手动细化。"
/>
</Field>