import { ArrowLeft, CheckCircle2, ImagePlus, Loader2, Play, Sparkles, Waves, } from 'lucide-react'; import { useEffect, useMemo, useState } from 'react'; import type { BigFishAssetSlotResponse, BigFishGameDraftResponse, BigFishLevelBlueprintResponse, BigFishSessionSnapshotResponse, ExecuteBigFishActionRequest, } from '../../../packages/shared/src/contracts/bigFish'; import { PlatformActionButton } from '../common/PlatformActionButton'; import { PlatformEmptyState } from '../common/PlatformEmptyState'; import { PlatformFieldLabel } from '../common/PlatformFieldLabel'; import { PlatformIconBadge } from '../common/PlatformIconBadge'; import { PlatformIconButton } from '../common/PlatformIconButton'; import { PlatformMediaFrame } from '../common/PlatformMediaFrame'; import { PlatformPillBadge } from '../common/PlatformPillBadge'; import { PlatformStatusMessage } from '../common/PlatformStatusMessage'; import { PlatformSubpanel } from '../common/PlatformSubpanel'; import { UnifiedConfirmDialog } from '../common/UnifiedConfirmDialog'; type BigFishAssetStudioTarget = | { kind: 'level_main_image'; level: BigFishLevelBlueprintResponse; } | { kind: 'level_motion'; level: BigFishLevelBlueprintResponse; motionKey: 'idle_float' | 'move_swim'; } | { kind: 'stage_background'; }; type BigFishResultViewProps = { session: BigFishSessionSnapshotResponse; isBusy?: boolean; error?: string | null; onBack: () => void; onDismissError?: () => void; onExecuteAction: (payload: ExecuteBigFishActionRequest) => void; onStartTestRun: () => void; }; function findAssetSlot( slots: BigFishAssetSlotResponse[], assetKind: string, level?: number, motionKey?: string, ) { return slots.find((slot) => { if (slot.assetKind !== assetKind) { return false; } if (level !== undefined && slot.level !== level) { return false; } if (motionKey !== undefined && slot.motionKey !== motionKey) { return false; } return true; }); } function assetReadyLabel(slot: BigFishAssetSlotResponse | undefined) { if (slot?.status !== 'ready') { return '待生成'; } return isBigFishPlaceholderAsset(slot) ? '占位已生成' : '已生成'; } function buildLevelAssetPreview(slot: BigFishAssetSlotResponse | undefined) { if (slot?.assetUrl) { return slot.assetUrl; } return null; } function isBigFishPlaceholderAsset(slot: BigFishAssetSlotResponse | undefined) { return Boolean(slot?.assetUrl?.includes('/generated-big-fish/')); } function buildStudioAssetPreview( slots: BigFishAssetSlotResponse[], target: BigFishAssetStudioTarget, ) { if (target.kind === 'stage_background') { return buildLevelAssetPreview(findAssetSlot(slots, 'stage_background')); } if (target.kind === 'level_main_image') { return buildLevelAssetPreview( findAssetSlot(slots, 'level_main_image', target.level.level), ); } return buildLevelAssetPreview( findAssetSlot(slots, 'level_motion', target.level.level, target.motionKey), ); } function BigFishAssetStudioModal({ draft, target, previewUrl, isBusy, onClose, onExecuteAction, }: { draft: BigFishGameDraftResponse; target: BigFishAssetStudioTarget; previewUrl?: string | null; isBusy: boolean; onClose: () => void; onExecuteAction: (payload: ExecuteBigFishActionRequest) => void; }) { const title = target.kind === 'stage_background' ? '场地背景工坊' : target.kind === 'level_main_image' ? `Lv.${target.level.level} 主图工坊` : `Lv.${target.level.level} 动作工坊`; const prompt = target.kind === 'stage_background' ? draft.background.backgroundPromptSeed : target.kind === 'level_main_image' ? target.level.visualDescription || target.level.visualPromptSeed : target.motionKey === 'move_swim' ? target.level.moveMotionDescription || target.level.motionPromptSeed : target.level.idleMotionDescription || target.level.motionPromptSeed; const execute = () => { if (target.kind === 'stage_background') { onExecuteAction({ action: 'big_fish_generate_stage_background' }); return; } if (target.kind === 'level_main_image') { onExecuteAction({ action: 'big_fish_generate_level_main_image', level: target.level.level, }); return; } onExecuteAction({ action: 'big_fish_generate_level_motion', level: target.level.level, motionKey: target.motionKey, }); }; return (
{title}
{target.kind === 'stage_background' ? draft.background.theme : target.level.textDescription || target.level.oneLineFantasy}
PROMPT
{prompt}
关闭 {isBusy ? : null} 生成并应用正式图
); } function BigFishLevelCard({ level, slots, isBusy, onOpenStudio, }: { level: BigFishLevelBlueprintResponse; slots: BigFishAssetSlotResponse[]; isBusy: boolean; onOpenStudio: (target: BigFishAssetStudioTarget) => void; }) { const mainImageSlot = findAssetSlot(slots, 'level_main_image', level.level); const idleSlot = findAssetSlot( slots, 'level_motion', level.level, 'idle_float', ); const moveSlot = findAssetSlot( slots, 'level_motion', level.level, 'move_swim', ); const previewUrl = buildLevelAssetPreview(mainImageSlot); return (
} aspect="square" surface="none" className="h-24 w-24 shrink-0 rounded-[1.15rem] bg-[radial-gradient(circle_at_center,rgba(34,211,238,0.28),transparent_68%),linear-gradient(145deg,rgba(8,47,73,0.88),rgba(15,23,42,0.94))] text-white" />
LV.{level.level}
{level.name}
{level.isFinalLevel ? ( 终局 ) : null}
{level.oneLineFantasy}
猎物 {level.preyWindow.join('/') || '-'} 威胁 {level.threatWindow.join('/') || '-'} 主图 {assetReadyLabel(mainImageSlot)} 动作{' '} {[assetReadyLabel(idleSlot), assetReadyLabel(moveSlot)].join('/')}
{ onOpenStudio({ kind: 'level_main_image', level }); }} shape="pill" size="xs" className="px-3" > 主图 { onOpenStudio({ kind: 'level_motion', level, motionKey: 'idle_float', }); }} tone="ghost" shape="pill" size="xs" className="px-3" > 待机 { onOpenStudio({ kind: 'level_motion', level, motionKey: 'move_swim', }); }} tone="ghost" shape="pill" size="xs" className="px-3" > 移动
); } export function BigFishResultView({ session, isBusy = false, error = null, onBack, onDismissError, onExecuteAction, onStartTestRun, }: BigFishResultViewProps) { const [studioTarget, setStudioTarget] = useState(null); const [isPublishSubmitting, setIsPublishSubmitting] = useState(false); const draft = session.draft; const backgroundSlot = findAssetSlot(session.assetSlots, 'stage_background'); const backgroundPreviewUrl = buildLevelAssetPreview(backgroundSlot); const blockers = useMemo( () => session.assetCoverage.blockers.filter(Boolean), [session.assetCoverage.blockers], ); const isPublished = session.stage === 'published'; const canClickPublish = !isPublished && !isBusy; const studioPreviewUrl = useMemo(() => { if (!studioTarget) { return null; } return buildStudioAssetPreview(session.assetSlots, studioTarget); }, [session.assetSlots, studioTarget]); useEffect(() => { if (!isBusy || isPublished || error) { setIsPublishSubmitting(false); } }, [error, isBusy, isPublished]); if (!draft) { return (
还没有可编辑的玩法草稿
); } return (
} />
{ onStartTestRun(); }} surface="editorDark" tone="secondary" shape="pill" className="!border-white/16 !bg-white/12 !text-white hover:!bg-white/18" > 测试 { setIsPublishSubmitting(true); onExecuteAction({ action: 'big_fish_publish_game' }); }} surface="editorDark" tone="primary" shape="pill" className="!border-cyan-200/70 !bg-cyan-200 !text-slate-950 hover:!bg-cyan-100" > {isPublishSubmitting && isBusy && !isPublished ? ( ) : ( )} {isPublished ? '已发布' : isPublishSubmitting && isBusy ? '发布中' : '发布'}
{draft.title}
{draft.subtitle}
{draft.coreFun} {draft.ecologyTheme} {draft.runtimeParams.levelCount} 级
{draft.levels.map((level) => ( ))}
{studioTarget ? ( { setStudioTarget(null); }} onExecuteAction={(payload) => { onExecuteAction(payload); setStudioTarget(null); }} /> ) : null} {error ? ( { onDismissError?.(); }} /> ) : null}
); } function BigFishResultErrorModal({ message, onClose, }: { message: string; onClose: () => void; }) { return (
} label="发布失败提示" tone="danger" className="mt-0.5" />
{message}
); } export default BigFishResultView;