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}
关闭
{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;