import { ImagePlus, RefreshCcw, } from 'lucide-react'; import { type ChangeEvent, type ReactNode, useEffect, useMemo, useState, } from 'react'; import { createPortal } from 'react-dom'; import { ROLE_TEMPLATE_CHARACTERS } from '../data/characterPresets'; import { AnimationState, type Character, } from '../types'; import { buildAnimationClipFromVideoSource, normalizeMasterVisualSourceToDataUrl, readFileAsDataUrl, } from './asset-studio/characterAssetWorkflowModel'; import { type CharacterAnimationGenerationPayload, type CharacterAssetWorkflowCache, type CharacterVisualDraft, fetchCharacterWorkflowCache, generateCharacterAnimationDraft, generateCharacterPromptBundle, generateCharacterVisualCandidates, publishCharacterAnimationAssets, publishCharacterVisualAsset, saveCharacterWorkflowCache, } from './asset-studio/characterAssetWorkflowPersistence'; import { buildDefaultRolePromptBundle } from './asset-studio/customWorldRolePromptDefaults'; import { CharacterAnimator } from './CharacterAnimator'; type EditableCustomWorldRole = { id: string; name: string; title: string; role: string; description?: string; backstory?: string; personality?: string; motivation?: string; combatStyle?: string; tags?: string[]; templateCharacterId?: string; imageSrc?: string; generatedVisualAssetId?: string; generatedAnimationSetId?: string; animationMap?: Record; }; type CustomWorldAiActionConfig = { animation: AnimationState; label: string; templateId: string; fps: number; frameCount: number; durationSeconds: number; loop: boolean; }; const CORE_ACTIONS: CustomWorldAiActionConfig[] = [ { animation: AnimationState.IDLE, label: '待机', templateId: 'idle', fps: 8, frameCount: 8, durationSeconds: 4, loop: true, }, { animation: AnimationState.RUN, label: '奔跑', templateId: 'run', fps: 12, frameCount: 8, durationSeconds: 4, loop: true, }, { animation: AnimationState.ATTACK, label: '攻击', templateId: 'attack_slash', fps: 12, frameCount: 8, durationSeconds: 3, loop: false, }, { animation: AnimationState.HURT, label: '受击', templateId: 'hurt', fps: 10, frameCount: 6, durationSeconds: 3, loop: false, }, { animation: AnimationState.DIE, label: '死亡', templateId: 'die', fps: 8, frameCount: 8, durationSeconds: 4, loop: false, }, ]; function ModalShell({ title, subtitle, onClose, disableClose = false, children, }: { title: string; subtitle?: string; onClose: () => void; disableClose?: boolean; children: React.ReactNode; }) { 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 (