import { ArrowLeft } from 'lucide-react'; import type { CustomWorldGenerationProgress } from '../../packages/shared/src/contracts/runtime'; import type { CustomWorldStructuredAnchorEntry } from '../services/customWorldAgentGenerationProgress'; import { GenerationCurrentStepCard, GenerationPageBackdrop, GenerationProgressHero, } from './GenerationProgressHero'; 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 resolveCurrentGenerationStep( progress: CustomWorldGenerationProgress | null, ) { const steps = progress?.steps ?? []; return ( steps.find((step) => step.status === 'active') ?? steps[progress?.activeStepIndex ?? -1] ?? steps.find((step) => step.status === 'pending') ?? steps.at(-1) ?? null ); } function buildFallbackRenderKey( value: string | null | undefined, fallback: string, ) { const normalizedValue = value?.trim(); return normalizedValue ? normalizedValue : fallback; } export function CustomWorldGenerationView({ settingText, anchorEntries = [], progress, isGenerating, onBack, onEditSetting, onRetry, onInterrupt, backLabel = '返回', settingActionLabel = '修改设定', retryLabel = '重新开始生成', interruptLabel = '中断世界生成', settingTitle = '玩家设定', settingDescription = '这段文本会直接驱动本轮世界框架、角色与场景生成。', progressTitle = '生成进度', activeBadgeLabel = '世界建设中', idleBadgeLabel = '等待操作', structuredEmptyText = '正在整理当前设定结构,请稍后。', hideBatchModule = false, }: CustomWorldGenerationViewProps) { void hideBatchModule; const progressValue = getProgressPercentage(progress); const currentStep = resolveCurrentGenerationStep(progress); const currentStepProgress = currentStep ? getStepProgressPercentage(currentStep) : progressValue; const currentStepLabel = currentStep?.label ?? progress?.phaseLabel ?? '准备生成'; const currentStepStatusLabel = currentStep ? getStepStatusLabel(currentStep) : isGenerating ? '进行中' : '待处理'; const hasStructuredAnchors = anchorEntries.length > 0; // 允许不同生成场景按需隐藏第二模块的说明和次级返回动作。 const normalizedSettingActionLabel = settingActionLabel?.trim() ?? ''; const normalizedSettingDescription = settingDescription?.trim() ?? ''; const hasSettingActionLabel = normalizedSettingActionLabel.length > 0; const hasSettingDescription = normalizedSettingDescription.length > 0; const estimatedWaitText = progress?.estimatedRemainingMs != null ? formatDuration(progress.estimatedRemainingMs) : '校准中'; const elapsedText = progress != null ? formatDuration(progress.elapsedMs) : '启动中'; return (
{isGenerating ? activeBadgeLabel : idleBadgeLabel}
{!isGenerating ? ( <> {hasSettingActionLabel ? ( ) : null} ) : onInterrupt ? ( ) : null}
{settingTitle}
{hasSettingDescription ? (
{normalizedSettingDescription}
) : null}
{hasSettingActionLabel ? ( ) : null}
{hasStructuredAnchors ? (
{anchorEntries.map((entry, index) => (
{entry.label}
{entry.value}
))}
) : (
{settingText || structuredEmptyText}
)}
); }