This commit is contained in:
2026-04-25 22:38:03 +08:00
29 changed files with 1988 additions and 121 deletions

View File

@@ -1,4 +1,5 @@
import { AnimatePresence, motion } from 'motion/react';
import { Loader2 } from 'lucide-react';
import { AnimatePresence, motion } from 'motion/react';
import {
lazy,
Suspense,
@@ -9,6 +10,7 @@ import {
useState,
} from 'react';
import type { PublicUserSummary } from '../../../packages/shared/src/contracts/auth';
import type {
BigFishRuntimeSnapshotResponse,
BigFishSessionSnapshotResponse,
@@ -31,7 +33,6 @@ import type {
CustomWorldGalleryCard,
CustomWorldLibraryEntry,
} from '../../../packages/shared/src/contracts/runtime';
import type { PublicUserSummary } from '../../../packages/shared/src/contracts/auth';
import { buildCustomWorldPlayableCharacters } from '../../data/characterPresets';
import {
getPublicAuthUserByCode,
@@ -43,15 +44,22 @@ import {
getBigFishCreationSession,
streamBigFishCreationMessage,
} from '../../services/big-fish-creation';
import {
deleteBigFishWork,
listBigFishWorks,
} from '../../services/big-fish-works';
import {
startBigFishRuntimeRun,
submitBigFishRuntimeInput,
} from '../../services/big-fish-runtime';
import {
deleteBigFishWork,
listBigFishWorks,
} from '../../services/big-fish-works';
import { readCustomWorldAgentUiState } from '../../services/customWorldAgentUiState';
import {
buildBigFishGenerationAnchorEntries,
buildMiniGameDraftGenerationProgress,
buildPuzzleGenerationAnchorEntries,
createMiniGameDraftGenerationState,
type MiniGameDraftGenerationState,
} from '../../services/miniGameDraftGenerationProgress';
import { getPlatformProfileDashboard } from '../../services/platform-entry';
import {
createPuzzleAgentSession,
@@ -60,17 +68,17 @@ import {
streamPuzzleAgentMessage,
} from '../../services/puzzle-agent';
import { getPuzzleGalleryDetail, listPuzzleGallery } from '../../services/puzzle-gallery';
import { advanceLocalPuzzleNextLevel } from '../../services/puzzle-runtime';
import {
advanceLocalPuzzleLevel,
dragLocalPuzzlePiece,
startLocalPuzzleRun,
swapLocalPuzzlePieces,
} from '../../services/puzzle-runtime/puzzleLocalRuntime';
import { deletePuzzleWork, listPuzzleWorks } from '../../services/puzzle-works';
import { deleteRpgEntryWorldProfile } from '../../services/rpg-entry';
import { getRpgEntryWorldGalleryDetailByCode } from '../../services/rpg-entry/rpgEntryLibraryClient';
import { deleteRpgCreationAgentSession } from '../../services/rpg-creation';
import { rpgCreationPreviewAdapter } from '../../services/rpg-creation/rpgCreationPreviewAdapter';
import { deleteRpgEntryWorldProfile } from '../../services/rpg-entry';
import { getRpgEntryWorldGalleryDetailByCode } from '../../services/rpg-entry/rpgEntryLibraryClient';
import type { CustomWorldProfile } from '../../types';
import { useAuthUi } from '../auth/AuthUiContext';
import { CustomWorldCreationHub } from '../custom-world-home/CustomWorldCreationHub';
@@ -88,13 +96,13 @@ import {
type PlatformPublicGalleryCard,
} from '../rpg-entry/rpgEntryWorldPresentation';
import { PlatformEntryCreationTypeModal } from './PlatformEntryCreationTypeModal';
import type { PlatformCreationTypeId } from './platformEntryCreationTypes';
import { PlatformEntryHomeView } from './PlatformEntryHomeView';
import {
buildCreationHubFallbackItems,
normalizeAgentBackedProfile,
resolveRpgCreationErrorMessage,
} from './platformEntryShared';
import type { PlatformCreationTypeId } from './platformEntryCreationTypes';
import type { PlatformEntryFlowShellProps } from './platformEntryTypes';
import { PlatformEntryWorldDetailView } from './PlatformEntryWorldDetailView';
import { usePlatformEntryBootstrap } from './usePlatformEntryBootstrap';
@@ -341,6 +349,8 @@ export function PlatformEntryFlowShellImpl({
const [bigFishRun, setBigFishRun] =
useState<BigFishRuntimeSnapshotResponse | null>(null);
const [isBigFishLoadingLibrary, setIsBigFishLoadingLibrary] = useState(false);
const [bigFishGenerationState, setBigFishGenerationState] =
useState<MiniGameDraftGenerationState | null>(null);
const bigFishInputInFlightRef = useRef(false);
const [puzzleOperation, setPuzzleOperation] =
useState<PuzzleAgentOperationRecord | null>(null);
@@ -352,6 +362,10 @@ export function PlatformEntryFlowShellImpl({
useState<PuzzleWorkSummary | null>(null);
const [puzzleRun, setPuzzleRun] = useState<PuzzleRunSnapshot | null>(null);
const [isPuzzleLoadingLibrary, setIsPuzzleLoadingLibrary] = useState(false);
const [puzzleGenerationState, setPuzzleGenerationState] =
useState<MiniGameDraftGenerationState | null>(null);
const [isPuzzleNextLevelGenerating, setIsPuzzleNextLevelGenerating] =
useState(false);
const [isSearchingPublicCode, setIsSearchingPublicCode] = useState(false);
const [publicSearchError, setPublicSearchError] = useState<string | null>(null);
const [searchedPublicUser, setSearchedPublicUser] =
@@ -773,8 +787,44 @@ export function PlatformEntryFlowShellImpl({
onSessionOpened: () => {
setShowCreationTypeModal(false);
},
onActionComplete: ({ response, setSession }) => {
onActionComplete: ({ payload, response, setSession }) => {
setSession(response.session);
if (payload.action !== 'big_fish_compile_draft') {
return;
}
setBigFishGenerationState((current) =>
current
? {
...current,
phase: 'ready',
completedAssetCount: response.session.assetSlots.filter(
(slot) => slot.status === 'ready',
).length,
totalAssetCount: response.session.assetSlots.length,
}
: current,
);
},
beforeExecuteAction: ({ payload }) => {
if (payload.action !== 'big_fish_compile_draft') {
return;
}
setSelectionStage('big-fish-generating');
setBigFishGenerationState(createMiniGameDraftGenerationState('big-fish'));
},
onActionError: ({ payload, errorMessage }) => {
if (payload.action !== 'big_fish_compile_draft') {
return;
}
setBigFishGenerationState((current) =>
current
? {
...current,
phase: 'failed',
error: errorMessage,
}
: current,
);
},
});
@@ -784,7 +834,7 @@ export function PlatformEntryFlowShellImpl({
{ session: PuzzleAgentSessionSnapshot },
SendPuzzleAgentMessageRequest,
PuzzleAgentActionRequest,
{ operation: PuzzleAgentOperationRecord }
{ operation: PuzzleAgentOperationRecord; session: PuzzleAgentSessionSnapshot }
>({
client: {
createSession: createPuzzleAgentSession,
@@ -811,8 +861,9 @@ export function PlatformEntryFlowShellImpl({
onSessionOpened: () => {
setShowCreationTypeModal(false);
},
onActionComplete: async ({ payload, response, session, setSession }) => {
onActionComplete: async ({ payload, response, setSession }) => {
setPuzzleOperation(response.operation);
setSession(response.session);
if (payload.action === 'publish_puzzle_work') {
await Promise.allSettled([
@@ -821,21 +872,51 @@ export function PlatformEntryFlowShellImpl({
]);
}
const latestResponse = await getPuzzleAgentSession(session.sessionId);
const latestSession = latestResponse.session;
setSession(latestSession);
if (payload.action === 'compile_puzzle_draft') {
setPuzzleGenerationState((current) =>
current
? {
...current,
phase: 'ready',
completedAssetCount: 1,
totalAssetCount: 1,
}
: current,
);
}
if (
payload.action === 'publish_puzzle_work' &&
latestSession.publishedProfileId
response.session.publishedProfileId
) {
const galleryDetail = await getPuzzleGalleryDetail(
latestSession.publishedProfileId,
response.session.publishedProfileId,
);
setSelectedPuzzleDetail(galleryDetail.item);
setSelectionStage('puzzle-gallery-detail');
}
},
beforeExecuteAction: ({ payload }) => {
if (payload.action !== 'compile_puzzle_draft') {
return;
}
setSelectionStage('puzzle-generating');
setPuzzleGenerationState(createMiniGameDraftGenerationState('puzzle'));
},
onActionError: ({ payload, errorMessage }) => {
if (payload.action !== 'compile_puzzle_draft') {
return;
}
setPuzzleGenerationState((current) =>
current
? {
...current,
phase: 'failed',
error: errorMessage,
}
: current,
);
},
});
const bigFishSession = bigFishFlow.session;
@@ -906,12 +987,15 @@ export function PlatformEntryFlowShellImpl({
const leaveBigFishFlow = useCallback(() => {
setBigFishRun(null);
setBigFishGenerationState(null);
bigFishFlow.leaveFlow();
}, [bigFishFlow]);
const leavePuzzleFlow = useCallback(() => {
setPuzzleOperation(null);
setPuzzleRun(null);
setPuzzleGenerationState(null);
setIsPuzzleNextLevelGenerating(false);
puzzleFlow.leaveFlow();
}, [puzzleFlow]);
@@ -1039,9 +1123,35 @@ export function PlatformEntryFlowShellImpl({
return;
}
const currentLevel = puzzleRun.currentLevel;
if (!currentLevel || currentLevel.status !== 'cleared') {
return;
}
setIsPuzzleBusy(true);
setIsPuzzleNextLevelGenerating(true);
setPuzzleError(null);
setPuzzleRun(advanceLocalPuzzleLevel(puzzleRun));
}, [isPuzzleBusy, puzzleRun]);
try {
const { run } = await advanceLocalPuzzleNextLevel({
run: puzzleRun,
sourceSessionId:
selectedPuzzleDetail?.sourceSessionId ?? puzzleSession?.sessionId ?? null,
});
setPuzzleRun(run);
} catch (error) {
setPuzzleError(resolvePuzzleErrorMessage(error, '准备下一关失败。'));
} finally {
setIsPuzzleNextLevelGenerating(false);
setIsPuzzleBusy(false);
}
}, [
isPuzzleBusy,
puzzleRun,
puzzleSession,
resolvePuzzleErrorMessage,
selectedPuzzleDetail,
]);
const leaveAgentWorkspace = useCallback(() => {
enterCreateTab();
@@ -1713,6 +1823,49 @@ export function PlatformEntryFlowShellImpl({
</motion.div>
)}
{selectionStage === 'big-fish-generating' && (
<motion.div
key="big-fish-generating"
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -12 }}
className="flex h-full min-h-0 flex-col"
>
<Suspense
fallback={<LazyPanelFallback label="正在加载大鱼吃小鱼生成面板..." />}
>
<CustomWorldGenerationView
settingText={
bigFishSession?.lastAssistantReply ?? '正在整理当前玩法草稿。'
}
anchorEntries={buildBigFishGenerationAnchorEntries(bigFishSession)}
progress={buildMiniGameDraftGenerationProgress(
bigFishGenerationState,
)}
isGenerating={isBigFishBusy}
error={bigFishError}
onBack={leaveBigFishFlow}
onEditSetting={() => {
setSelectionStage('big-fish-agent-workspace');
}}
onRetry={() => {
void executeBigFishAction({ action: 'big_fish_compile_draft' });
}}
onInterrupt={undefined}
backLabel="返回创作中心"
settingActionLabel={null}
retryLabel="重新生成草稿"
settingTitle="当前玩法信息"
settingDescription={null}
progressTitle="大鱼吃小鱼草稿生成进度"
activeBadgeLabel="草稿生成中"
pausedBadgeLabel="草稿生成已暂停"
idleBadgeLabel="等待返回工作区"
/>
</Suspense>
</motion.div>
)}
{selectionStage === 'big-fish-result' && bigFishSession?.draft && (
<motion.div
key="big-fish-result"
@@ -1796,6 +1949,49 @@ export function PlatformEntryFlowShellImpl({
</motion.div>
)}
{selectionStage === 'puzzle-generating' && (
<motion.div
key="puzzle-generating"
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -12 }}
className="flex h-full min-h-0 flex-col"
>
<Suspense
fallback={<LazyPanelFallback label="正在加载拼图生成面板..." />}
>
<CustomWorldGenerationView
settingText={
puzzleSession?.lastAssistantReply ?? '正在整理当前拼图草稿。'
}
anchorEntries={buildPuzzleGenerationAnchorEntries(puzzleSession)}
progress={buildMiniGameDraftGenerationProgress(
puzzleGenerationState,
)}
isGenerating={isPuzzleBusy}
error={puzzleError}
onBack={leavePuzzleFlow}
onEditSetting={() => {
setSelectionStage('puzzle-agent-workspace');
}}
onRetry={() => {
void executePuzzleAction({ action: 'compile_puzzle_draft' });
}}
onInterrupt={undefined}
backLabel="返回创作中心"
settingActionLabel={null}
retryLabel="重新生成草稿"
settingTitle="当前拼图信息"
settingDescription={null}
progressTitle="拼图草稿生成进度"
activeBadgeLabel="草稿生成中"
pausedBadgeLabel="草稿生成已暂停"
idleBadgeLabel="等待返回工作区"
/>
</Suspense>
</motion.div>
)}
{selectionStage === 'puzzle-result' && puzzleSession?.draft && (
<motion.div
key="puzzle-result"
@@ -1852,7 +2048,7 @@ export function PlatformEntryFlowShellImpl({
>
<PuzzleRuntimeShell
run={puzzleRun}
isBusy={isPuzzleBusy}
isBusy={isPuzzleBusy || isPuzzleNextLevelGenerating}
error={puzzleError}
onBack={() => {
setSelectionStage('puzzle-gallery-detail');
@@ -1867,6 +2063,17 @@ export function PlatformEntryFlowShellImpl({
void advancePuzzleLevel();
}}
/>
{isPuzzleNextLevelGenerating ? (
<div className="fixed inset-0 z-[120] flex items-center justify-center bg-slate-950/62 px-5 backdrop-blur-sm">
<div className="flex max-w-[18rem] flex-col items-center gap-3 rounded-[1.5rem] border border-white/12 bg-slate-950/92 px-6 py-5 text-center text-white shadow-[0_28px_80px_rgba(0,0,0,0.35)]">
<Loader2 className="h-6 w-6 animate-spin text-amber-200" />
<div className="text-sm font-bold"></div>
<div className="text-xs leading-5 text-white/68">
广
</div>
</div>
</div>
) : null}
</motion.div>
)}