This commit is contained in:
2026-04-28 19:36:39 +08:00
parent a9febe7678
commit f0471a4f8d
206 changed files with 18456 additions and 10133 deletions

View File

@@ -18,10 +18,9 @@ import { readFileAsDataUrl } from '../asset-studio/characterAssetWorkflowModel';
import {
type CharacterAssetWorkflowCache,
type CharacterVisualDraft,
fetchCharacterWorkflowCache,
saveCharacterWorkflowCache,
putCharacterRoleAssetWorkflow,
resolveCharacterRoleAssetWorkflow,
} from '../asset-studio/characterAssetWorkflowPersistence';
import { buildDefaultRolePromptBundle } from '../asset-studio/customWorldRolePromptDefaults';
import { buildProjectPixelStyleReferenceBoard } from '../asset-studio/projectPixelStyleReference';
import { useAuthUi } from '../auth/AuthUiContext';
import { CharacterAnimator } from '../CharacterAnimator';
@@ -51,42 +50,6 @@ function clampAnimationPlaybackRate(value: number) {
);
}
function buildDefaultAnimationPromptTextByKey(defaultText: string) {
return CORE_ACTIONS.reduce<Partial<Record<AnimationState, string>>>(
(result, action) => ({
...result,
[action.animation]: defaultText,
}),
{},
);
}
function pickCachedAnimationPromptTextByKey(
cache: CharacterAssetWorkflowCache,
fallbackText: string,
preferFreshRoleText: boolean,
) {
const fromCache = cache.animationPromptTextByKey ?? {};
return CORE_ACTIONS.reduce<Partial<Record<AnimationState, string>>>(
(result, action) => {
const cachedText = fromCache[action.animation]?.trim();
const legacyText = cache.animationPromptText?.trim();
return {
...result,
[action.animation]: preferFreshRoleText
? fallbackText
: cachedText && !isLegacyGeneratedActionDescription(cachedText)
? cachedText
: legacyText && !isLegacyGeneratedActionDescription(legacyText)
? legacyText
: fallbackText,
};
},
{},
);
}
function roundAnimationFps(value: number) {
return Math.round(value * 100) / 100;
}
@@ -321,37 +284,6 @@ 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>,
@@ -561,13 +493,9 @@ export function RpgCreationRoleAssetStudioModal({
role.visualDescription,
],
);
const initialPromptBundle = useMemo(
() => buildDefaultRolePromptBundle(baseRole),
[baseRole],
);
const [visualPromptText, setVisualPromptText] = useState(
initialPromptBundle.visualPromptText,
);
const [defaultAnimationPromptText, setDefaultAnimationPromptText] =
useState('');
const [visualPromptText, setVisualPromptText] = useState('');
const [referenceImageDataUrls, setReferenceImageDataUrls] = useState<
string[]
>([]);
@@ -586,11 +514,7 @@ export function RpgCreationRoleAssetStudioModal({
);
const [animationPromptTextByKey, setAnimationPromptTextByKey] = useState<
Partial<Record<AnimationState, string>>
>(() =>
buildDefaultAnimationPromptTextByKey(
initialPromptBundle.animationPromptText,
),
);
>({});
const [animationStatusByKey, setAnimationStatusByKey] = useState<
Partial<Record<AnimationState, string | null>>
>({});
@@ -655,7 +579,7 @@ export function RpgCreationRoleAssetStudioModal({
CORE_ACTIONS[0]!;
const animationPromptText =
animationPromptTextByKey[selectedAnimation] ??
initialPromptBundle.animationPromptText;
defaultAnimationPromptText;
const previewCharacter = useMemo(
() =>
buildAnimationPreviewCharacter({
@@ -727,12 +651,9 @@ export function RpgCreationRoleAssetStudioModal({
useEffect(() => {
let cancelled = false;
setWorkingRole(baseRole);
setVisualPromptText(initialPromptBundle.visualPromptText);
setAnimationPromptTextByKey(
buildDefaultAnimationPromptTextByKey(
initialPromptBundle.animationPromptText,
),
);
setDefaultAnimationPromptText('');
setVisualPromptText('');
setAnimationPromptTextByKey({});
setReferenceImageDataUrls([]);
setVisualDrafts([]);
setSelectedVisualDraftId('');
@@ -744,50 +665,52 @@ export function RpgCreationRoleAssetStudioModal({
setSaveStatus(null);
setIsHydratingCache(true);
void fetchCharacterWorkflowCache(baseRole.id, cacheScopeId)
void resolveCharacterRoleAssetWorkflow({
characterId: baseRole.id,
cacheScopeId,
role: {
...baseRole,
animationMap:
(baseRole.animationMap as Record<string, unknown> | undefined) ??
null,
},
})
.then((result) => {
if (cancelled || !result.cache) {
if (cancelled) {
return;
}
const cache = result.cache;
if (cacheScopeId && cache.cacheScopeId !== cacheScopeId) {
return;
}
const { workflow } = result;
const nextRole = mergeRole(baseRole, {
imageSrc: cache.imageSrc ?? baseRole.imageSrc,
imageSrc: workflow.imageSrc ?? baseRole.imageSrc,
generatedVisualAssetId:
cache.generatedVisualAssetId ?? baseRole.generatedVisualAssetId,
workflow.generatedVisualAssetId ?? baseRole.generatedVisualAssetId,
generatedAnimationSetId:
cache.generatedAnimationSetId ?? baseRole.generatedAnimationSetId,
workflow.generatedAnimationSetId ??
baseRole.generatedAnimationSetId,
animationMap:
(cache.animationMap as EditableCustomWorldRole['animationMap']) ??
(workflow.animationMap as EditableCustomWorldRole['animationMap']) ??
baseRole.animationMap,
});
setWorkingRole(nextRole);
setVisualPromptText(
!baseRole.visualDescription?.trim() &&
cache.visualPromptText &&
!isLegacyGeneratedVisualDescription(cache.visualPromptText)
? cache.visualPromptText
: initialPromptBundle.visualPromptText,
setDefaultAnimationPromptText(
workflow.defaultPromptBundle.animationPromptText,
);
setVisualPromptText(workflow.visualPromptText);
setAnimationPromptTextByKey(
pickCachedAnimationPromptTextByKey(
cache,
initialPromptBundle.animationPromptText,
Boolean(baseRole.actionDescription?.trim()),
),
workflow.animationPromptTextByKey as Partial<
Record<AnimationState, string>
>,
);
setVisualDrafts(cache.visualDrafts ?? []);
setVisualDrafts(workflow.visualDrafts ?? []);
setSelectedVisualDraftId(
cache.selectedVisualDraftId || cache.visualDrafts?.[0]?.id || '',
workflow.selectedVisualDraftId || workflow.visualDrafts?.[0]?.id || '',
);
setSelectedAnimation(
CORE_ACTIONS.some(
(item) => item.animation === cache.selectedAnimation,
(item) => item.animation === workflow.selectedAnimation,
)
? (cache.selectedAnimation as AnimationState)
? (workflow.selectedAnimation as AnimationState)
: (CORE_ACTIONS[0]?.animation ?? AnimationState.IDLE),
);
})
@@ -801,7 +724,7 @@ export function RpgCreationRoleAssetStudioModal({
return () => {
cancelled = true;
};
}, [baseRole, cacheScopeId, initialPromptBundle, roleSnapshotKey]);
}, [baseRole, cacheScopeId, roleSnapshotKey]);
useEffect(() => {
if (isHydratingCache) {
@@ -826,7 +749,7 @@ export function RpgCreationRoleAssetStudioModal({
unknown
> | null,
};
void saveCharacterWorkflowCache(payload).catch(() => undefined);
void putCharacterRoleAssetWorkflow(payload).catch(() => undefined);
}, 350);
return () => {