import { type ChangeEvent, type CSSProperties, type ReactNode, useEffect, useMemo, useState, } from 'react'; import { createPortal } from 'react-dom'; import { ROLE_TEMPLATE_CHARACTERS } from '../../data/characterPresets'; import { AnimationState, type Character, type CharacterAnimationConfig, } from '../../types'; import { readFileAsDataUrl } from '../asset-studio/characterAssetWorkflowModel'; import { type CharacterAssetWorkflowCache, type CharacterVisualDraft, fetchCharacterWorkflowCache, saveCharacterWorkflowCache, } 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'; import { CORE_ACTIONS, type CustomWorldAiActionConfig, type EditableCustomWorldRole, } from './roleAssetStudioModel'; import { RpgCreationRoleAnimationSection } from './RpgCreationRoleAnimationSection'; import { RpgCreationRoleAssetStudioFooter } from './RpgCreationRoleAssetStudioFooter'; import { RpgCreationRoleVisualSection } from './RpgCreationRoleVisualSection'; import { useRoleAnimationWorkflow } from './useRoleAnimationWorkflow'; import { useRoleVisualCandidateWorkflow } from './useRoleVisualCandidateWorkflow'; const DEFAULT_ANIMATION_PLAYBACK_RATE = 0.75; const MIN_ANIMATION_PLAYBACK_RATE = 0.25; const MAX_ANIMATION_PLAYBACK_RATE = 1.5; function clampAnimationPlaybackRate(value: number) { if (!Number.isFinite(value)) { return DEFAULT_ANIMATION_PLAYBACK_RATE; } return Math.min( MAX_ANIMATION_PLAYBACK_RATE, Math.max(MIN_ANIMATION_PLAYBACK_RATE, value), ); } function buildDefaultAnimationPromptTextByKey(defaultText: string) { return CORE_ACTIONS.reduce>>( (result, action) => ({ ...result, [action.animation]: defaultText, }), {}, ); } function pickCachedAnimationPromptTextByKey( cache: CharacterAssetWorkflowCache, fallbackText: string, preferFreshRoleText: boolean, ) { const fromCache = cache.animationPromptTextByKey ?? {}; return CORE_ACTIONS.reduce>>( (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; } function ModalShell({ title, subtitle, onClose, disableClose = false, children, }: { title: string; subtitle?: string; onClose: () => void; disableClose?: boolean; children: React.ReactNode; }) { const authUi = useAuthUi(); const platformThemeClass = authUi?.platformTheme === 'dark' ? 'platform-theme--dark' : 'platform-theme--light'; return (
{ if (event.target === event.currentTarget) { onClose(); } } } >
event.stopPropagation()} >
{title}
{subtitle ? (
{subtitle}
) : null}
{children}
); } function PortalModalShell(props: { title: string; subtitle?: string; onClose: () => void; disableClose?: boolean; children: React.ReactNode; }) { if (typeof document === 'undefined') { return null; } return createPortal(, document.body); } function Section({ title, children, }: { title: string; children: React.ReactNode; }) { return (
{title}
{children}
); } function Field({ label, children, }: { label: string; children: React.ReactNode; }) { return ( ); } function TextArea({ value, onChange, rows = 4, placeholder, readOnly = false, }: { value: string; onChange: (value: string) => void; rows?: number; placeholder?: string; readOnly?: boolean; }) { return (