import { motion } from 'motion/react'; import type { CSSProperties } from 'react'; import { useEffect, useState } from 'react'; import type { CustomWorldGenerationProgress } from '../../packages/shared/src/contracts/runtime'; import type { CustomWorldStructuredAnchorEntry } from '../services/customWorldAgentGenerationProgress'; interface CustomWorldGenerationViewProps { settingText: string; anchorEntries?: CustomWorldStructuredAnchorEntry[]; progress: CustomWorldGenerationProgress | null; isGenerating: boolean; error: string | null; onBack: () => void; onEditSetting: () => void; onRetry: () => void; onInterrupt?: () => void; backLabel?: string; settingActionLabel?: string | null; retryLabel?: string; interruptLabel?: string; settingTitle?: string; settingDescription?: string | null; progressTitle?: string; activeBadgeLabel?: string; pausedBadgeLabel?: string; idleBadgeLabel?: string; structuredEmptyText?: string; hideBatchModule?: boolean; } function formatDuration(ms: number) { const safeMs = Math.max(0, Math.round(ms)); const totalSeconds = Math.ceil(safeMs / 1000); const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; if (minutes <= 0) { return `${Math.max(1, seconds)} 秒`; } if (seconds === 0) { return `${minutes} 分钟`; } return `${minutes} 分 ${seconds} 秒`; } function getProgressPercentage(progress: CustomWorldGenerationProgress | null) { return Math.max(0, Math.min(100, progress?.overallProgress ?? 0)); } function getStepProgressPercentage(step: { completed: number; total: number; status: string; }) { if (step.status === 'completed') { return 100; } if (step.total <= 0) { return 0; } return Math.max( 0, Math.min(100, Math.round((step.completed / step.total) * 100)), ); } function getStepStatusLabel(step: { status: string }) { if (step.status === 'completed') { return '完成'; } if (step.status === 'active') { return '进行中'; } return '待处理'; } function buildFallbackRenderKey( value: string | null | undefined, fallback: string, ) { const normalizedValue = value?.trim(); return normalizedValue ? normalizedValue : fallback; } function useIsMobileGenerationLayout() { const [isMobile, setIsMobile] = useState(() => { if ( typeof window === 'undefined' || typeof window.matchMedia !== 'function' ) { return false; } return window.matchMedia('(max-width: 639px)').matches; }); useEffect(() => { if ( typeof window === 'undefined' || typeof window.matchMedia !== 'function' ) { return undefined; } const mediaQuery = window.matchMedia('(max-width: 639px)'); const syncMobileLayout = () => { setIsMobile(mediaQuery.matches); }; syncMobileLayout(); if (typeof mediaQuery.addEventListener === 'function') { mediaQuery.addEventListener('change', syncMobileLayout); return () => { mediaQuery.removeEventListener('change', syncMobileLayout); }; } mediaQuery.addListener(syncMobileLayout); return () => { mediaQuery.removeListener(syncMobileLayout); }; }, []); return isMobile; } export function CustomWorldGenerationView({ settingText, anchorEntries = [], progress, isGenerating, error, onBack, onEditSetting, onRetry, onInterrupt, backLabel = '返回', settingActionLabel = '修改设定', retryLabel = '重新开始生成', interruptLabel = '中断世界生成', settingTitle = '玩家设定', settingDescription = '这段文本会直接驱动本轮世界框架、角色与场景生成。', progressTitle = '生成进度', activeBadgeLabel = '世界建设中', pausedBadgeLabel = '生成已暂停', idleBadgeLabel = '等待操作', structuredEmptyText = '正在整理当前设定结构,请稍后。', hideBatchModule = false, }: CustomWorldGenerationViewProps) { const isMobileGenerationLayout = useIsMobileGenerationLayout(); const progressValue = getProgressPercentage(progress); const steps = progress?.steps ?? []; const hasStructuredAnchors = anchorEntries.length > 0; // 允许不同生成场景按需隐藏第二模块的说明和次级返回动作。 const normalizedSettingActionLabel = settingActionLabel?.trim() ?? ''; const normalizedSettingDescription = settingDescription?.trim() ?? ''; const hasSettingActionLabel = normalizedSettingActionLabel.length > 0; const hasSettingDescription = normalizedSettingDescription.length > 0; const shouldHideBatchModule = hideBatchModule || progressTitle === '拼图草稿生成进度' || progressTitle === '抓大鹅草稿生成进度'; const estimatedWaitText = progress?.estimatedRemainingMs != null ? `预计还需 ${formatDuration(progress.estimatedRemainingMs)}` : '正在校准预计等待时间'; const elapsedText = progress != null ? `已耗时 ${formatDuration(progress.elapsedMs)}` : '正在启动世界生成'; return (