import { Loader2 } from 'lucide-react'; import { AnimatePresence, motion } from 'motion/react'; import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState, } from 'react'; import type { PublicUserSummary } from '../../../packages/shared/src/contracts/auth'; import type { BigFishRuntimeSnapshotResponse, BigFishSessionSnapshotResponse, ExecuteBigFishActionRequest, SendBigFishMessageRequest, SubmitBigFishInputRequest, } from '../../../packages/shared/src/contracts/bigFish'; import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/bigFishWorkSummary'; import type { CreateMatch3DSessionRequest, ExecuteMatch3DActionRequest, Match3DActionResponse, Match3DAgentSessionSnapshot, Match3DSessionResponse, SendMatch3DMessageRequest, } from '../../../packages/shared/src/contracts/match3dAgent'; import type { Match3DRunSnapshot } from '../../../packages/shared/src/contracts/match3dRuntime'; import type { Match3DWorkProfile, Match3DWorkSummary, } from '../../../packages/shared/src/contracts/match3dWorks'; import type { PuzzleAgentActionRequest, PuzzleAgentOperationRecord, } from '../../../packages/shared/src/contracts/puzzleAgentActions'; import type { PuzzleResultDraft } from '../../../packages/shared/src/contracts/puzzleAgentDraft'; import type { CreatePuzzleAgentSessionRequest, PuzzleAgentSessionSnapshot, SendPuzzleAgentMessageRequest, } from '../../../packages/shared/src/contracts/puzzleAgentSession'; import type { PuzzleRunSnapshot, PuzzleRuntimePropKind, SubmitPuzzleLeaderboardRequest, } from '../../../packages/shared/src/contracts/puzzleRuntimeSession'; import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary'; import type { CustomWorldGalleryCard, CustomWorldLibraryEntry, ProfilePlayedWorkSummary, ProfilePlayStatsResponse, ProfileSaveArchiveResumeResponse, ProfileSaveArchiveSummary, } from '../../../packages/shared/src/contracts/runtime'; import { buildCustomWorldPlayableCharacters } from '../../data/characterPresets'; import { buildPublicWorkStagePath, pushAppHistoryPath, } from '../../routing/appPageRoutes'; import { getPublicAuthUserByCode, getPublicAuthUserById, } from '../../services/authService'; import { createBigFishCreationSession, executeBigFishCreationAction, getBigFishCreationSession, streamBigFishCreationMessage, } from '../../services/big-fish-creation'; import { likeBigFishGalleryWork, listBigFishGallery, remixBigFishGalleryWork, } from '../../services/big-fish-gallery'; import { advanceLocalBigFishRuntimeRun, recordBigFishPlay, startLocalBigFishRuntimeRun, } from '../../services/big-fish-runtime'; import { deleteBigFishWork, listBigFishWorks, } from '../../services/big-fish-works'; import { readCustomWorldAgentUiState, shouldRestoreCustomWorldAgentUiState, } from '../../services/customWorldAgentUiState'; import { match3dCreationClient } from '../../services/match3d-creation'; import { clickMatch3DItem, finishMatch3DTimeUp, restartMatch3DRun, startMatch3DRun, stopMatch3DRun, } from '../../services/match3d-runtime'; import { deleteMatch3DWork, getMatch3DWorkDetail, listMatch3DGallery, listMatch3DWorks, } from '../../services/match3d-works'; import { buildBigFishGenerationAnchorEntries, buildMiniGameDraftGenerationProgress, buildPuzzleGenerationAnchorEntries, createMiniGameDraftGenerationState, type MiniGameDraftGenerationState, } from '../../services/miniGameDraftGenerationProgress'; import { getPlatformProfileDashboard } from '../../services/platform-entry/platformProfileClient'; import { buildBigFishPublicWorkCode, buildMatch3DPublicWorkCode, buildPuzzlePublicWorkCode, isSameBigFishPublicWorkCode, isSameMatch3DPublicWorkCode, isSamePuzzlePublicWorkCode, } from '../../services/publicWorkCode'; import { createPuzzleAgentSession, executePuzzleAgentAction, getPuzzleAgentSession, streamPuzzleAgentMessage, } from '../../services/puzzle-agent'; import { getPuzzleGalleryDetail, likePuzzleGalleryWork, listPuzzleGallery, remixPuzzleGalleryWork, } from '../../services/puzzle-gallery'; import { advanceLocalPuzzleNextLevel, advancePuzzleNextLevel, getPuzzleRun, startPuzzleRun, submitPuzzleLeaderboard, updatePuzzleRunPause, usePuzzleRuntimeProp as consumePuzzleRuntimeProp, } from '../../services/puzzle-runtime'; import { applyLocalPuzzleFreezeTime, dragLocalPuzzlePiece, extendLocalPuzzleTime, isLocalPuzzleRun, refreshLocalPuzzleTimer, resolvePuzzleRestartLevelId, restartLocalPuzzleLevel, setLocalPuzzlePaused, startLocalPuzzleRun, submitLocalPuzzleLeaderboard, swapLocalPuzzlePieces, } from '../../services/puzzle-runtime/puzzleLocalRuntime'; import { claimPuzzleWorkPointIncentive, deletePuzzleWork, listPuzzleWorks, } from '../../services/puzzle-works'; import { deleteRpgCreationAgentSession } from '../../services/rpg-creation'; import { rpgCreationPreviewAdapter } from '../../services/rpg-creation/rpgCreationPreviewAdapter'; import { deleteRpgEntryWorldProfile, getRpgEntryWorldGalleryDetailByCode, likeRpgEntryWorldGallery, recordRpgEntryWorldGalleryPlay, remixRpgEntryWorldGallery, } from '../../services/rpg-entry/rpgEntryLibraryClient'; import { getRpgProfilePlayStats } from '../../services/rpg-entry/rpgProfileClient'; import { requestRpgRuntimeJson } from '../../services/rpg-runtime/rpgRuntimeRequest'; import type { CustomWorldProfile } from '../../types'; import { useAuthUi } from '../auth/AuthUiContext'; import { PublishShareModal } from '../common/PublishShareModal'; import type { PublishShareModalPayload } from '../common/publishShareModalModel'; import { UnifiedModal } from '../common/UnifiedModal'; import { isBigFishGalleryEntry, isMatch3DGalleryEntry, isPuzzleGalleryEntry, mapBigFishWorkToPlatformGalleryCard, mapMatch3DWorkToPlatformGalleryCard, mapPuzzleWorkToPlatformGalleryCard, type PlatformPublicGalleryCard, } from '../rpg-entry/rpgEntryWorldPresentation'; import { useRpgCreationAgentOperationPolling } from '../rpg-entry/useRpgCreationAgentOperationPolling'; import { useRpgCreationEnterWorld } from '../rpg-entry/useRpgCreationEnterWorld'; import { useRpgCreationResultAutosave } from '../rpg-entry/useRpgCreationResultAutosave'; import { useRpgCreationSessionController } from '../rpg-entry/useRpgCreationSessionController'; import { PlatformEntryCreationTypeModal } from './PlatformEntryCreationTypeModal'; import type { PlatformCreationTypeId } from './platformEntryCreationTypes'; import { isPlatformCreationTypeVisible } from './platformEntryCreationTypes'; import { PlatformEntryHomeView, type PlatformHomeTab, } from './PlatformEntryHomeView'; import { buildCreationHubFallbackItems, resolveRpgCreationErrorMessage, } from './platformEntryShared'; import type { PlatformEntryFlowShellProps } from './platformEntryTypes'; import { PlatformEntryWorldDetailView } from './PlatformEntryWorldDetailView'; import { PlatformWorkDetailView } from './PlatformWorkDetailView'; import { usePlatformCreationAgentFlowController } from './usePlatformCreationAgentFlowController'; import { usePlatformEntryBootstrap } from './usePlatformEntryBootstrap'; import { usePlatformEntryLibraryDetail } from './usePlatformEntryLibraryDetail'; import { usePlatformEntryNavigation } from './usePlatformEntryNavigation'; type AgentResultPublishGateView = { blockers: string[]; publishReady: boolean; }; type PuzzleDetailReturnTarget = { tab: PlatformHomeTab; }; type PuzzleRuntimeReturnStage = | 'puzzle-result' | 'puzzle-gallery-detail' | 'work-detail' | 'platform'; type BigFishRuntimeReturnStage = 'big-fish-result' | 'work-detail' | 'platform'; type PuzzleSaveArchiveState = { runtimeKind?: unknown; entryProfileId?: unknown; currentProfileId?: unknown; currentLevelId?: unknown; }; type DeleteCreationWorkConfirmation = { id: string; title: string; detail: string; run: () => void; }; async function resumePuzzleProfileSaveArchiveRaw(worldKey: string) { return requestRpgRuntimeJson< ProfileSaveArchiveResumeResponse >( `/profile/save-archives/${encodeURIComponent(worldKey)}`, { method: 'POST' }, '恢复拼图存档失败', ); } type AgentResultBlockerView = { code?: string; message: string; }; const AGENT_RESULT_STRUCTURAL_BLOCKER_CODES = new Set([ 'publish_missing_world_hook', 'publish_missing_player_premise', 'publish_missing_core_conflict', 'publish_missing_main_chapter', 'publish_missing_first_act', ]); function getPlatformPublicGalleryEntryTime(entry: PlatformPublicGalleryCard) { const rawTime = entry.publishedAt ?? entry.updatedAt; const timestamp = new Date(rawTime).getTime(); return Number.isNaN(timestamp) ? 0 : timestamp; } function getPlatformPublicGalleryEntryKey(entry: PlatformPublicGalleryCard) { const kind = isBigFishGalleryEntry(entry) ? 'big-fish' : isPuzzleGalleryEntry(entry) ? 'puzzle' : isMatch3DGalleryEntry(entry) ? 'match3d' : 'rpg'; return `${kind}:${entry.ownerUserId}:${entry.profileId}`; } function isSamePlatformPublicGalleryEntry( left: PlatformPublicGalleryCard, right: PlatformPublicGalleryCard, ) { return ( getPlatformPublicGalleryEntryKey(left) === getPlatformPublicGalleryEntryKey(right) ); } function mergePlatformPublicGalleryEntries( rpgEntries: CustomWorldGalleryCard[], puzzleEntries: PlatformPublicGalleryCard[], ) { const entryMap = new Map(); [...rpgEntries, ...puzzleEntries].forEach((entry) => { entryMap.set(getPlatformPublicGalleryEntryKey(entry), entry); }); return Array.from(entryMap.values()).sort( (left, right) => getPlatformPublicGalleryEntryTime(right) - getPlatformPublicGalleryEntryTime(left), ); } function mapRpgGalleryCardToPublicWorkDetail( entry: CustomWorldGalleryCard, ): PlatformPublicGalleryCard { return entry; } function mapPuzzleWorkToPublicWorkDetail( item: PuzzleWorkSummary, ): PlatformPublicGalleryCard { return mapPuzzleWorkToPlatformGalleryCard(item); } function resolveVisiblePuzzleDetailCoverCount( entry: PlatformPublicGalleryCard | null, run: PuzzleRunSnapshot | null, ) { if (!entry || !isPuzzleGalleryEntry(entry)) { return 1; } if (run?.entryProfileId !== entry.profileId) { return 1; } // 中文注释:封面首图永远公开,后续封面跟随当前玩家本次 run 的通关进度即时解锁。 return Math.max(1, run.clearedLevelCount + 1); } function mapMatch3DWorkToPublicWorkDetail( item: Match3DWorkSummary, ): PlatformPublicGalleryCard { return mapMatch3DWorkToPlatformGalleryCard(item); } function mapBigFishWorkToPublicWorkDetail( item: BigFishWorkSummary, ): PlatformPublicGalleryCard { return mapBigFishWorkToPlatformGalleryCard(item); } function mapPublicWorkDetailToMatch3DWork( entry: PlatformPublicGalleryCard, ): Match3DWorkSummary | null { if (!isMatch3DGalleryEntry(entry)) { return null; } return { workId: entry.workId, profileId: entry.profileId, ownerUserId: entry.ownerUserId, sourceSessionId: 'sourceSessionId' in entry && typeof entry.sourceSessionId === 'string' ? entry.sourceSessionId : null, gameName: entry.worldName, themeText: entry.themeTags[0] ?? '经典消除', summary: entry.summaryText, tags: entry.themeTags, coverImageSrc: entry.coverImageSrc, referenceImageSrc: null, clearCount: 12, difficulty: 4, publicationStatus: 'published', playCount: entry.playCount ?? 0, updatedAt: entry.updatedAt, publishedAt: entry.publishedAt, publishReady: true, }; } function buildMatch3DProfileFromSession( session: Match3DAgentSessionSnapshot | null, ): Match3DWorkProfile | null { const draft = session?.draft; if (!session || !draft?.profileId) { return null; } const now = session.updatedAt || new Date().toISOString(); return { workId: draft.profileId, profileId: draft.profileId, ownerUserId: 'current-user', sourceSessionId: session.sessionId, gameName: draft.gameName, themeText: draft.themeText, summary: draft.summary ?? draft.summaryText ?? '', tags: draft.tags, coverImageSrc: draft.coverImageSrc ?? draft.referenceImageSrc ?? null, referenceImageSrc: draft.referenceImageSrc ?? null, clearCount: draft.clearCount, difficulty: draft.difficulty, publicationStatus: 'draft', playCount: 0, updatedAt: now, publishedAt: null, publishReady: Boolean(draft.publishReady), }; } function mapPublicWorkDetailToPuzzleWork( entry: PlatformPublicGalleryCard, ): PuzzleWorkSummary | null { if (!isPuzzleGalleryEntry(entry)) { return null; } return { workId: entry.workId, profileId: entry.profileId, ownerUserId: entry.ownerUserId, sourceSessionId: 'sourceSessionId' in entry && typeof entry.sourceSessionId === 'string' ? entry.sourceSessionId : null, authorDisplayName: entry.authorDisplayName, levelName: entry.worldName, summary: entry.summaryText, themeTags: entry.themeTags, coverImageSrc: entry.coverImageSrc, publicationStatus: 'published', updatedAt: entry.updatedAt, publishedAt: entry.publishedAt, playCount: entry.playCount ?? 0, remixCount: entry.remixCount ?? 0, likeCount: entry.likeCount ?? 0, pointIncentiveTotalHalfPoints: 0, pointIncentiveClaimedPoints: 0, pointIncentiveTotalPoints: 0, pointIncentiveClaimablePoints: 0, publishReady: true, levels: entry.coverSlides?.map((slide, index) => ({ levelId: slide.id || `puzzle-level-${index + 1}`, levelName: slide.label, pictureDescription: entry.summaryText, candidates: [], selectedCandidateId: null, coverImageSrc: slide.imageSrc, coverAssetId: null, generationStatus: 'ready' as const, })) ?? [], }; } function mapPublicWorkDetailToBigFishWork( entry: PlatformPublicGalleryCard, ): BigFishWorkSummary | null { if (!isBigFishGalleryEntry(entry)) { return null; } const levelCount = Number.parseInt( entry.themeTags.find((tag) => /^\d+级$/u.test(tag))?.replace('级', '') ?? '0', 10, ); return { workId: entry.workId, sourceSessionId: entry.profileId, ownerUserId: entry.ownerUserId, authorDisplayName: entry.authorDisplayName, title: entry.worldName, subtitle: entry.subtitle, summary: entry.summaryText, coverImageSrc: entry.coverImageSrc, status: 'published', updatedAt: entry.updatedAt, publishedAt: entry.publishedAt, publishReady: true, levelCount: Number.isNaN(levelCount) ? 0 : levelCount, levelMainImageReadyCount: 0, levelMotionReadyCount: 0, backgroundReady: Boolean(entry.coverImageSrc), playCount: entry.playCount ?? 0, remixCount: entry.remixCount ?? 0, likeCount: entry.likeCount ?? 0, }; } function mergePuzzleWorkSummary( current: PuzzleWorkSummary, updated: PuzzleWorkSummary, ): PuzzleWorkSummary { return current.profileId === updated.profileId ? updated : current; } function mergeBigFishWorkSummary( current: BigFishWorkSummary, updated: BigFishWorkSummary, ): BigFishWorkSummary { return current.sourceSessionId === updated.sourceSessionId ? updated : current; } async function resolvePublicWorkAuthorSummary( entry: PlatformPublicGalleryCard, ): Promise { if ('authorPublicUserCode' in entry && entry.authorPublicUserCode?.trim()) { try { return await getPublicAuthUserByCode(entry.authorPublicUserCode); } catch { if (!entry.ownerUserId.trim()) { return null; } } } if (entry.ownerUserId.trim()) { return getPublicAuthUserById(entry.ownerUserId); } return null; } function readProfileTextField( profile: CustomWorldProfile | null, paths: string[], ) { for (const path of paths) { let current: unknown = profile; for (const segment of path.split('.')) { if (!current || typeof current !== 'object') { current = null; break; } current = (current as Record)[segment]; } if (typeof current === 'string' && current.trim()) { return current.trim(); } } return null; } function hasProfileTextArray(profile: CustomWorldProfile | null, key: string) { const value = profile ? (profile as unknown as Record)[key] : null; return Array.isArray(value) ? value.some((entry) => typeof entry === 'string' && entry.trim()) : false; } function hasProfileArray(profile: CustomWorldProfile | null, key: string) { const value = profile ? (profile as unknown as Record)[key] : null; return Array.isArray(value) && value.length > 0; } function hasSceneAct(profile: CustomWorldProfile | null) { const rawProfile = profile as unknown as Record | null; const chapters = rawProfile && (Array.isArray(rawProfile.sceneChapterBlueprints) ? rawProfile.sceneChapterBlueprints : Array.isArray(rawProfile.sceneChapters) ? rawProfile.sceneChapters : []); return Array.isArray(chapters) ? chapters.some((chapter) => { const acts = chapter && typeof chapter === 'object' ? (chapter as Record).acts : null; return Array.isArray(acts) && acts.length > 0; }) : false; } function isAgentResultStructuralBlockerResolved( profile: CustomWorldProfile, code: string | undefined, ) { if (!code || !AGENT_RESULT_STRUCTURAL_BLOCKER_CODES.has(code)) { return false; } if (code === 'publish_missing_world_hook') { return Boolean( readProfileTextField(profile, [ 'worldHook', 'creatorIntent.worldHook', 'anchorContent.worldPromise', 'anchorContent.worldPromise.hook', 'settingText', ]), ); } if (code === 'publish_missing_player_premise') { return Boolean( readProfileTextField(profile, [ 'playerPremise', 'creatorIntent.playerPremise', 'anchorContent.playerEntryPoint', 'anchorContent.playerEntryPoint.openingIdentity', 'anchorContent.playerEntryPoint.openingProblem', 'anchorContent.playerEntryPoint.entryMotivation', ]), ); } if (code === 'publish_missing_core_conflict') { return hasProfileTextArray(profile, 'coreConflicts'); } if (code === 'publish_missing_main_chapter') { return ( hasProfileArray(profile, 'chapters') || hasProfileArray(profile, 'sceneChapterBlueprints') || hasProfileArray(profile, 'sceneChapters') ); } return hasSceneAct(profile); } function buildAgentResultPublishGateView( profile: CustomWorldProfile | null, fallbackBlockers: AgentResultBlockerView[], fallbackPublishReady: boolean, ): AgentResultPublishGateView { if (!profile) { return { blockers: fallbackBlockers.map((entry) => entry.message), publishReady: fallbackPublishReady, }; } const blockers = fallbackBlockers .filter( (entry) => !isAgentResultStructuralBlockerResolved(profile, entry.code), ) .map((entry) => entry.message); return { blockers, publishReady: blockers.length === 0, }; } function buildPuzzleResultProfileId(sessionId: string | null | undefined) { const normalizedSessionId = sessionId?.trim(); if (!normalizedSessionId) { return null; } const stableSuffix = normalizedSessionId.startsWith('puzzle-session-') ? normalizedSessionId.slice('puzzle-session-'.length) : normalizedSessionId; return `puzzle-profile-${stableSuffix}`; } function buildPuzzleCompileActionFromFormPayload( payload: CreatePuzzleAgentSessionRequest | null, ): PuzzleAgentActionRequest { const workTitle = payload?.workTitle?.trim() || payload?.seedText?.trim(); const workDescription = payload?.workDescription?.trim(); const pictureDescription = payload?.pictureDescription?.trim(); return { action: 'compile_puzzle_draft', promptText: pictureDescription || workTitle, ...(workTitle ? { workTitle } : {}), ...(workDescription ? { workDescription } : {}), ...(pictureDescription ? { pictureDescription } : {}), referenceImageSrc: payload?.referenceImageSrc || null, imageModel: payload?.imageModel ?? null, candidateCount: 1, }; } function buildPuzzleFormPayloadFromSession( session: PuzzleAgentSessionSnapshot, ): CreatePuzzleAgentSessionRequest { const formDraft = session.draft?.formDraft; const workTitle = formDraft?.workTitle?.trim() || session.draft?.workTitle?.trim() || session.draft?.levelName?.trim() || session.anchorPack.themePromise.value.trim() || session.seedText?.trim() || ''; const workDescription = formDraft?.workDescription?.trim() || session.draft?.workDescription?.trim() || session.draft?.summary?.trim() || ''; const pictureDescription = formDraft?.pictureDescription?.trim() || session.draft?.levels?.[0]?.pictureDescription?.trim() || session.anchorPack.visualSubject.value.trim() || ''; return { seedText: workTitle, workTitle, workDescription, pictureDescription, referenceImageSrc: null, imageModel: null, }; } function buildPuzzleFormPayloadFromAction( payload: PuzzleAgentActionRequest, ): CreatePuzzleAgentSessionRequest | null { if ( payload.action !== 'compile_puzzle_draft' && payload.action !== 'save_puzzle_form_draft' ) { return null; } const workTitle = payload.workTitle?.trim() ?? ''; const workDescription = payload.workDescription?.trim() ?? ''; const pictureDescription = payload.pictureDescription?.trim() || payload.promptText?.trim() || ''; return { seedText: workTitle, workTitle, workDescription, pictureDescription, referenceImageSrc: payload.action === 'compile_puzzle_draft' ? (payload.referenceImageSrc ?? null) : null, imageModel: payload.action === 'compile_puzzle_draft' ? (payload.imageModel ?? null) : null, }; } function isPuzzleFormOnlyDraft(session: PuzzleAgentSessionSnapshot | null) { return Boolean( session?.stage === 'collecting_anchors' && session.draft?.formDraft, ); } function isEmptyPuzzleFormOnlyDraft( session: PuzzleAgentSessionSnapshot | null, ) { if (!isPuzzleFormOnlyDraft(session)) { return false; } const formDraft = session?.draft?.formDraft; return !( session?.seedText?.trim() || formDraft?.workTitle?.trim() || formDraft?.workDescription?.trim() || formDraft?.pictureDescription?.trim() ); } const CustomWorldGenerationView = lazy(async () => { const module = await import('../CustomWorldGenerationView'); return { default: module.CustomWorldGenerationView, }; }); const RpgCreationResultView = lazy(async () => { const module = await import('../rpg-creation-result/RpgCreationResultView'); return { default: module.RpgCreationResultView, }; }); const CustomWorldAgentWorkspace = lazy(async () => { const module = await import( '../custom-world-agent/CustomWorldAgentWorkspace' ); return { default: module.CustomWorldAgentWorkspace, }; }); const BigFishAgentWorkspace = lazy(async () => { const module = await import('../big-fish-creation/BigFishAgentWorkspace'); return { default: module.BigFishAgentWorkspace, }; }); const BigFishResultView = lazy(async () => { const module = await import('../big-fish-result/BigFishResultView'); return { default: module.BigFishResultView, }; }); const BigFishRuntimeShell = lazy(async () => { const module = await import('../big-fish-runtime/BigFishRuntimeShell'); return { default: module.BigFishRuntimeShell, }; }); const Match3DAgentWorkspace = lazy(async () => { const module = await import('../match3d-creation/Match3DAgentWorkspace'); return { default: module.Match3DAgentWorkspace, }; }); const Match3DResultView = lazy(async () => { const module = await import('../match3d-result/Match3DResultView'); return { default: module.Match3DResultView, }; }); const Match3DRuntimeShell = lazy(async () => { const module = await import('../match3d-runtime/Match3DRuntimeShell'); return { default: module.Match3DRuntimeShell, }; }); const CustomWorldCreationHub = lazy(async () => { const module = await import('../custom-world-home/CustomWorldCreationHub'); return { default: module.CustomWorldCreationHub, }; }); const PuzzleAgentWorkspace = lazy(async () => { const module = await import('../puzzle-agent/PuzzleAgentWorkspace'); return { default: module.PuzzleAgentWorkspace, }; }); const PuzzleResultView = lazy(async () => { const module = await import('../puzzle-result/PuzzleResultView'); return { default: module.PuzzleResultView, }; }); const PuzzleGalleryDetailView = lazy(async () => { const module = await import('../puzzle-gallery/PuzzleGalleryDetailView'); return { default: module.PuzzleGalleryDetailView, }; }); const PuzzleRuntimeShell = lazy(async () => { const module = await import('../puzzle-runtime/PuzzleRuntimeShell'); return { default: module.PuzzleRuntimeShell, }; }); function LazyPanelFallback({ label }: { label: string }) { return (
{label}
); } function mergePuzzleServiceRuntimeState( currentRun: PuzzleRunSnapshot, serviceRun: PuzzleRunSnapshot, ): PuzzleRunSnapshot { if (!currentRun.currentLevel || !serviceRun.currentLevel) { return currentRun; } const serviceLevel = serviceRun.currentLevel; if ( currentRun.currentLevel.status === 'cleared' && serviceLevel.status !== 'cleared' ) { return { ...currentRun, recommendedNextProfileId: serviceRun.recommendedNextProfileId, nextLevelMode: serviceRun.nextLevelMode, nextLevelProfileId: serviceRun.nextLevelProfileId, nextLevelId: serviceRun.nextLevelId, recommendedNextWorks: serviceRun.recommendedNextWorks, leaderboardEntries: currentRun.currentLevel.leaderboardEntries.length > 0 ? currentRun.currentLevel.leaderboardEntries : currentRun.leaderboardEntries, }; } const leaderboardEntries = serviceLevel.leaderboardEntries.length > 0 ? serviceLevel.leaderboardEntries : serviceRun.leaderboardEntries; return { ...currentRun, recommendedNextProfileId: serviceRun.recommendedNextProfileId, nextLevelMode: serviceRun.nextLevelMode, nextLevelProfileId: serviceRun.nextLevelProfileId, nextLevelId: serviceRun.nextLevelId, recommendedNextWorks: serviceRun.recommendedNextWorks, leaderboardEntries, currentLevel: { ...currentRun.currentLevel, status: serviceLevel.status, startedAtMs: serviceLevel.startedAtMs, clearedAtMs: serviceLevel.clearedAtMs, elapsedMs: serviceLevel.elapsedMs, timeLimitMs: serviceLevel.timeLimitMs, remainingMs: serviceLevel.remainingMs, pausedAccumulatedMs: serviceLevel.pausedAccumulatedMs, pauseStartedAtMs: serviceLevel.pauseStartedAtMs, freezeAccumulatedMs: serviceLevel.freezeAccumulatedMs, freezeStartedAtMs: serviceLevel.freezeStartedAtMs, freezeUntilMs: serviceLevel.freezeUntilMs, leaderboardEntries, }, }; } export function PlatformEntryFlowShellImpl({ selectionStage, setSelectionStage, hasSavedGame, savedSnapshot, handleContinueGame, handleStartNewGame, handleCustomWorldSelect, initialPublicWorkCode, }: PlatformEntryFlowShellProps) { const authUi = useAuthUi(); const [showCreationTypeModal, setShowCreationTypeModal] = useState(false); const [selectedDetailEntry, setSelectedDetailEntry] = useState | null>(null); const [selectedPublicWorkDetail, setSelectedPublicWorkDetail] = useState(null); const [selectedPublicWorkAuthor, setSelectedPublicWorkAuthor] = useState(null); const publicWorkAuthorRequestKeyRef = useRef(0); const [publicWorkDetailError, setPublicWorkDetailError] = useState< string | null >(null); const [isPublicWorkDetailBusy, setIsPublicWorkDetailBusy] = useState(false); const [bigFishWorks, setBigFishWorks] = useState([]); const [bigFishGalleryEntries, setBigFishGalleryEntries] = useState< BigFishWorkSummary[] >([]); const [match3dWorks, setMatch3DWorks] = useState([]); const [match3dGalleryEntries, setMatch3DGalleryEntries] = useState< Match3DWorkSummary[] >([]); const [match3dProfile, setMatch3DProfile] = useState(null); const [match3dRun, setMatch3DRun] = useState(null); const [match3dRuntimeReturnStage, setMatch3DRuntimeReturnStage] = useState< 'match3d-result' | 'work-detail' >('match3d-result'); const [isMatch3DLoadingLibrary, setIsMatch3DLoadingLibrary] = useState(false); const [bigFishRun, setBigFishRun] = useState(null); const [bigFishRuntimeShare, setBigFishRuntimeShare] = useState<{ title: string; publicWorkCode: string; } | null>(null); const [bigFishRuntimeWork, setBigFishRuntimeWork] = useState(null); const [bigFishRuntimeStartedAt, setBigFishRuntimeStartedAt] = useState< number | null >(null); const [bigFishRuntimeReturnStage, setBigFishRuntimeReturnStage] = useState('platform'); const [isBigFishLoadingLibrary, setIsBigFishLoadingLibrary] = useState(false); const [bigFishGenerationState, setBigFishGenerationState] = useState(null); const [, setPuzzleOperation] = useState( null, ); const [puzzleWorks, setPuzzleWorks] = useState([]); const [puzzleGalleryEntries, setPuzzleGalleryEntries] = useState< PuzzleWorkSummary[] >([]); const [selectedPuzzleDetail, setSelectedPuzzleDetail] = useState(null); const [puzzleDetailReturnTarget, setPuzzleDetailReturnTarget] = useState(null); const [puzzleRuntimeReturnStage, setPuzzleRuntimeReturnStage] = useState('puzzle-gallery-detail'); const [isPuzzleLeaderboardBusy, setIsPuzzleLeaderboardBusy] = useState(false); const submittedPuzzleLeaderboardKeysRef = useRef(new Set()); const [puzzleRun, setPuzzleRun] = useState(null); const puzzleRunRef = useRef(null); const [isPuzzleLoadingLibrary, setIsPuzzleLoadingLibrary] = useState(false); const [puzzleGenerationState, setPuzzleGenerationState] = useState(null); const [puzzleFormDraftPayload, setPuzzleFormDraftPayload] = useState(null); const [isPuzzleNextLevelGenerating, setIsPuzzleNextLevelGenerating] = useState(false); const [isSearchingPublicCode, setIsSearchingPublicCode] = useState(false); const [publicSearchError, setPublicSearchError] = useState( null, ); const [searchedPublicUser, setSearchedPublicUser] = useState(null); const [deletingCreationWorkId, setDeletingCreationWorkId] = useState< string | null >(null); const [pendingDeleteCreationWork, setPendingDeleteCreationWork] = useState(null); const [ claimingPuzzlePointIncentiveProfileId, setClaimingPuzzlePointIncentiveProfileId, ] = useState(null); const [publishSharePayload, setPublishSharePayload] = useState(null); const isBigFishCreationVisible = isPlatformCreationTypeVisible('big-fish'); const [profilePlayStats, setProfilePlayStats] = useState(null); const [profilePlayStatsError, setProfilePlayStatsError] = useState< string | null >(null); const [isProfilePlayStatsLoading, setIsProfilePlayStatsLoading] = useState(false); const [isProfilePlayStatsOpen, setIsProfilePlayStatsOpen] = useState(false); const hadReadableProtectedDataRef = useRef(false); const hasInitialAgentSession = Boolean( readCustomWorldAgentUiState().activeSessionId && shouldRestoreCustomWorldAgentUiState(), ); const handledInitialPublicWorkCodeRef = useRef(null); const platformBootstrap = usePlatformEntryBootstrap({ user: authUi?.user, canAccessProtectedData: authUi?.canAccessProtectedData, getProfileDashboard: getPlatformProfileDashboard, handleContinueGame, hasInitialAgentSession, }); const entryNavigation = usePlatformEntryNavigation({ setSelectionStage, setSelectedDetailEntry, }); const { setPlatformTab } = platformBootstrap; const enterCreateTab = useCallback(() => { // 只依赖稳定的 setter,避免把 bootstrap 对象的 render 级引用变化 // 传导成 Agent session 恢复 effect 的重复触发。 setPlatformTab('create'); }, [setPlatformTab]); const resolveBigFishErrorMessage = useCallback( (error: unknown, fallback: string) => resolveRpgCreationErrorMessage(error, fallback), [], ); const resolvePuzzleErrorMessage = useCallback( (error: unknown, fallback: string) => resolveRpgCreationErrorMessage(error, fallback), [], ); const resolveMatch3DErrorMessage = useCallback( (error: unknown, fallback: string) => resolveRpgCreationErrorMessage(error, fallback), [], ); const refreshBigFishShelf = useCallback(async () => { setIsBigFishLoadingLibrary(true); try { const worksResponse = await listBigFishWorks(); setBigFishWorks(worksResponse.items); setBigFishError(null); } catch (error) { setBigFishError( resolveBigFishErrorMessage(error, '读取大鱼吃小鱼作品列表失败。'), ); } finally { setIsBigFishLoadingLibrary(false); } }, [resolveBigFishErrorMessage]); const refreshBigFishGallery = useCallback(async () => { try { const galleryResponse = await listBigFishGallery(); setBigFishGalleryEntries(galleryResponse.items); return galleryResponse.items; } catch (error) { setBigFishGalleryEntries([]); setBigFishError( resolveBigFishErrorMessage(error, '读取大鱼吃小鱼广场失败。'), ); return []; } }, [resolveBigFishErrorMessage]); const refreshMatch3DShelf = useCallback(async () => { setIsMatch3DLoadingLibrary(true); try { const worksResponse = await listMatch3DWorks(); setMatch3DWorks(worksResponse.items); setMatch3DError(null); } catch (error) { setMatch3DError( resolveMatch3DErrorMessage(error, '读取抓大鹅作品列表失败。'), ); } finally { setIsMatch3DLoadingLibrary(false); } }, [resolveMatch3DErrorMessage]); const refreshMatch3DGallery = useCallback(async () => { try { const galleryResponse = await listMatch3DGallery(); setMatch3DGalleryEntries(galleryResponse.items); return galleryResponse.items; } catch { // 中文注释:公开广场是首页展示数据,失败时只降级为空列表; // 不写入创作错误态,避免挡住抓大鹅共创入口。 setMatch3DGalleryEntries([]); return []; } }, []); const refreshPuzzleShelf = useCallback(async () => { setIsPuzzleLoadingLibrary(true); try { const worksResponse = await listPuzzleWorks(); setPuzzleWorks(worksResponse.items); setPuzzleError(null); } catch (error) { setPuzzleError( resolvePuzzleErrorMessage(error, '读取拼图作品列表失败。'), ); } finally { setIsPuzzleLoadingLibrary(false); } }, [resolvePuzzleErrorMessage]); const refreshPuzzleGallery = useCallback(async () => { try { const galleryResponse = await listPuzzleGallery(); setPuzzleGalleryEntries(galleryResponse.items); return galleryResponse.items; } catch (error) { setPuzzleGalleryEntries([]); setPuzzleError(resolvePuzzleErrorMessage(error, '读取拼图广场失败。')); return []; } }, [resolvePuzzleErrorMessage]); const sessionController = useRpgCreationSessionController({ userId: authUi?.user?.id, openLoginModal: authUi?.openLoginModal, selectionStage, setSelectionStage, enterCreateTab, onSessionOpened: () => { setShowCreationTypeModal(false); }, }); useRpgCreationAgentOperationPolling({ activeAgentSessionId: sessionController.activeAgentSessionId, activeAgentOperationId: sessionController.activeAgentOperationId, userId: authUi?.user?.id, setAgentOperation: sessionController.setAgentOperation, persistAgentUiState: sessionController.persistAgentUiState, syncAgentSessionSnapshot: sessionController.syncAgentSessionSnapshot, }); const autosaveCoordinator = useRpgCreationResultAutosave({ selectionStage, activeAgentSessionId: sessionController.activeAgentSessionId, generatedCustomWorldProfile: sessionController.generatedCustomWorldProfile, isAgentDraftResultView: sessionController.isAgentDraftResultView, userId: authUi?.user?.id, setGeneratedCustomWorldProfile: sessionController.setGeneratedCustomWorldProfile, setAgentOperation: sessionController.setAgentOperation, setSavedCustomWorldEntries: platformBootstrap.setSavedCustomWorldEntries, setSelectedDetailEntry, refreshCustomWorldWorks: platformBootstrap.refreshCustomWorldWorks, persistAgentUiState: sessionController.persistAgentUiState, syncAgentSessionSnapshot: sessionController.syncAgentSessionSnapshot, syncAgentCreationResultView: sessionController.syncAgentCreationResultView, buildDraftResultProfile: (view) => rpgCreationPreviewAdapter.buildPreviewFromResultView(view), }); const detailNavigation = usePlatformEntryLibraryDetail({ userId: authUi?.user?.id, selectedDetailEntry, setSelectedDetailEntry, savedCustomWorldEntries: platformBootstrap.savedCustomWorldEntries, setSavedCustomWorldEntries: platformBootstrap.setSavedCustomWorldEntries, setGeneratedCustomWorldProfile: sessionController.setGeneratedCustomWorldProfile, setCustomWorldError: sessionController.setCustomWorldError, setCustomWorldAutoSaveError: autosaveCoordinator.setCustomWorldAutoSaveError, setCustomWorldAutoSaveState: autosaveCoordinator.setCustomWorldAutoSaveState, setCustomWorldGenerationViewSource: sessionController.setCustomWorldGenerationViewSource, setCustomWorldResultViewSource: sessionController.setCustomWorldResultViewSource, setSelectionStage, setPlatformTabToCreate: enterCreateTab, setPlatformError: platformBootstrap.setPlatformError, appendBrowseHistoryEntry: platformBootstrap.appendBrowseHistoryEntry, refreshCustomWorldWorks: platformBootstrap.refreshCustomWorldWorks, refreshPublishedGallery: platformBootstrap.refreshPublishedGallery, persistAgentUiState: sessionController.persistAgentUiState, syncAgentCreationResultView: sessionController.syncAgentCreationResultView, buildDraftResultProfile: (view) => rpgCreationPreviewAdapter.buildPreviewFromResultView(view), suppressAgentDraftResultAutoOpen: sessionController.suppressAgentDraftResultAutoOpen, releaseAgentDraftResultAutoOpenSuppression: sessionController.releaseAgentDraftResultAutoOpenSuppression, resetAutoSaveTrackingToIdle: autosaveCoordinator.resetAutoSaveTrackingToIdle, markAutoSavedProfile: autosaveCoordinator.markAutoSavedProfile, }); const enterWorldCoordinator = useRpgCreationEnterWorld({ isAgentDraftResultView: sessionController.isAgentDraftResultView, activeAgentSessionId: sessionController.activeAgentSessionId, generatedCustomWorldProfile: sessionController.generatedCustomWorldProfile, handleCustomWorldSelect, syncAgentDraftResultProfile: autosaveCoordinator.syncAgentDraftResultProfile, executePublishWorld: async () => { const latestSession = await autosaveCoordinator.executeAgentActionAndWait( { action: 'publish_world', }, ); // 发布动作会在后端同步 gallery 投影;前端发布完成后立即刷新首页/分类页共用的公开作品列表。 await Promise.allSettled([ platformBootstrap.refreshPublishedGallery(), platformBootstrap.refreshCustomWorldWorks(), isBigFishCreationVisible ? refreshBigFishGallery() : Promise.resolve([] as BigFishWorkSummary[]), refreshMatch3DGallery(), refreshPuzzleGallery(), ]); return latestSession; }, syncAgentCreationResultView: sessionController.syncAgentCreationResultView, setGeneratedCustomWorldProfile: sessionController.setGeneratedCustomWorldProfile, }); const previewCustomWorldCharacters = useMemo( () => sessionController.generatedCustomWorldProfile ? buildCustomWorldPlayableCharacters( sessionController.generatedCustomWorldProfile, ) : [], [sessionController.generatedCustomWorldProfile], ); const agentResultPreview = sessionController.agentSession?.resultPreview ?? null; const agentResultPreviewBlockers = useMemo( () => agentResultPreview?.blockers ?? [], [agentResultPreview], ); const agentResultPublishGateView = useMemo( () => buildAgentResultPublishGateView( sessionController.generatedCustomWorldProfile, agentResultPreviewBlockers, Boolean(agentResultPreview?.publishReady), ), [ agentResultPreview?.publishReady, agentResultPreviewBlockers, sessionController.generatedCustomWorldProfile, ], ); const agentResultPreviewQualityFindings = useMemo( () => agentResultPreview?.qualityFindings ?? [], [agentResultPreview], ); const openPublishShareModal = useCallback( (payload: PublishShareModalPayload) => { const publicWorkCode = payload.publicWorkCode.trim(); if (!publicWorkCode) { return; } setPublishSharePayload({ ...payload, publicWorkCode, title: payload.title.trim() || '我的作品', }); }, [], ); const openRpgPublishShareModal = useCallback( async (profile: CustomWorldProfile | null | undefined) => { const profileId = profile?.id?.trim(); if (!profileId) { return; } const profileName = profile?.name?.trim() || '我的作品'; const galleryEntries = await platformBootstrap .refreshPublishedGallery() .catch(() => [] as CustomWorldGalleryCard[]); const galleryEntry = galleryEntries.find( (entry) => entry.profileId === profileId, ); const publicWorkCode = galleryEntry?.publicWorkCode?.trim(); if (!publicWorkCode) { return; } openPublishShareModal({ title: galleryEntry?.worldName || profileName, publicWorkCode, stage: 'work-detail', }); }, [openPublishShareModal, platformBootstrap], ); const agentResultPreviewSourceLabel = useMemo(() => { if (!agentResultPreview?.source) { return null; } if (agentResultPreview.source === 'published_profile') { return '已发布世界'; } if (agentResultPreview.source === 'session_preview') { return '会话预览'; } return '服务端预览'; }, [agentResultPreview]); const featuredGalleryEntries = useMemo(() => { const bigFishPublicEntries = isBigFishCreationVisible ? bigFishGalleryEntries.map(mapBigFishWorkToPlatformGalleryCard) : []; const match3dPublicEntries = match3dGalleryEntries.map( mapMatch3DWorkToPlatformGalleryCard, ); const puzzlePublicEntries = puzzleGalleryEntries.map( mapPuzzleWorkToPlatformGalleryCard, ); return mergePlatformPublicGalleryEntries( platformBootstrap.publishedGalleryEntries, [ ...bigFishPublicEntries, ...match3dPublicEntries, ...puzzlePublicEntries, ], ).slice(0, 6); }, [ isBigFishCreationVisible, bigFishGalleryEntries, match3dGalleryEntries, platformBootstrap.publishedGalleryEntries, puzzleGalleryEntries, ]); const latestGalleryEntries = useMemo( () => mergePlatformPublicGalleryEntries( platformBootstrap.publishedGalleryEntries, [ ...(isBigFishCreationVisible ? bigFishGalleryEntries.map(mapBigFishWorkToPlatformGalleryCard) : []), ...match3dGalleryEntries.map(mapMatch3DWorkToPlatformGalleryCard), ...puzzleGalleryEntries.map(mapPuzzleWorkToPlatformGalleryCard), ], ), [ isBigFishCreationVisible, bigFishGalleryEntries, match3dGalleryEntries, platformBootstrap.publishedGalleryEntries, puzzleGalleryEntries, ], ); const creationHubItems = platformBootstrap.customWorldWorkEntries.length > 0 ? platformBootstrap.customWorldWorkEntries : buildCreationHubFallbackItems( platformBootstrap.savedCustomWorldEntries, ); const resultViewError = autosaveCoordinator.customWorldAutoSaveError ?? sessionController.customWorldError; const isSelectedPublicWorkOwned = Boolean( authUi?.user?.id && selectedPublicWorkDetail?.ownerUserId === authUi.user.id, ); const selectedPublicWorkActionMode = isSelectedPublicWorkOwned ? 'edit' : 'remix'; useEffect(() => { if ( selectionStage === 'custom-world-result' && !sessionController.generatedCustomWorldProfile ) { setSelectionStage(selectedDetailEntry ? 'detail' : 'platform'); } }, [ selectedDetailEntry, selectionStage, sessionController.generatedCustomWorldProfile, setSelectionStage, ]); const runProtectedAction = useCallback( (action: () => void) => { if (!authUi?.requireAuth) { action(); return; } authUi.requireAuth(action); }, [authUi], ); const requestDeleteCreationWork = useCallback( (confirmation: DeleteCreationWorkConfirmation) => { if (deletingCreationWorkId) { return; } runProtectedAction(() => { setPendingDeleteCreationWork(confirmation); }); }, [deletingCreationWorkId, runProtectedAction], ); const closeDeleteCreationWorkConfirmation = useCallback(() => { if (deletingCreationWorkId) { return; } setPendingDeleteCreationWork(null); }, [deletingCreationWorkId]); const confirmDeleteCreationWork = useCallback(() => { const confirmation = pendingDeleteCreationWork; if (!confirmation || deletingCreationWorkId) { return; } setPendingDeleteCreationWork(null); confirmation.run(); }, [deletingCreationWorkId, pendingDeleteCreationWork]); const prepareCreationLaunch = useCallback(() => { if (sessionController.isCreatingAgentSession) { return false; } if (!hasSavedGame) { handleStartNewGame(); } sessionController.setCreationTypeError(null); return true; }, [handleStartNewGame, hasSavedGame, sessionController]); const openCreationTypePicker = useCallback(() => { if (!prepareCreationLaunch()) { return; } setShowCreationTypeModal(true); }, [prepareCreationLaunch]); const bigFishFlow = usePlatformCreationAgentFlowController< BigFishSessionSnapshotResponse, Record, { session: BigFishSessionSnapshotResponse }, SendBigFishMessageRequest, ExecuteBigFishActionRequest, { session: BigFishSessionSnapshotResponse } >({ client: { createSession: createBigFishCreationSession, getSession: getBigFishCreationSession, streamMessage: streamBigFishCreationMessage, executeAction: executeBigFishCreationAction, selectSession: (response) => response.session, }, createPayload: {}, workspaceStage: 'big-fish-agent-workspace', resultStage: 'big-fish-result', platformStage: 'platform', isCompileAction: (payload) => payload.action === 'big_fish_compile_draft', resolveErrorMessage: resolveBigFishErrorMessage, errorMessages: { open: '开启大鱼吃小鱼共创工作台失败。', restoreMissingSession: '这份大鱼吃小鱼草稿缺少会话信息,请重新开始创作。', restore: '读取大鱼吃小鱼创作草稿失败。', submit: '发送大鱼吃小鱼共创消息失败。', execute: '执行大鱼吃小鱼操作失败。', }, enterCreateTab, setSelectionStage, onSessionOpened: () => { setShowCreationTypeModal(false); }, onActionComplete: ({ payload, response, setSession }) => { setSession(response.session); if (payload.action === 'big_fish_publish_game') { void refreshBigFishShelf(); void refreshBigFishGallery(); openPublishShareModal({ title: response.session.draft?.title ?? '大鱼吃小鱼', publicWorkCode: buildBigFishPublicWorkCode(response.session.sessionId), stage: 'big-fish-runtime', }); } 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, ); }, }); const match3dFlow = usePlatformCreationAgentFlowController< Match3DAgentSessionSnapshot, CreateMatch3DSessionRequest, Match3DSessionResponse, SendMatch3DMessageRequest, ExecuteMatch3DActionRequest, Match3DActionResponse >({ client: { createSession: match3dCreationClient.createSession, getSession: match3dCreationClient.getSession, streamMessage: match3dCreationClient.streamMessage, executeAction: match3dCreationClient.executeAction, selectSession: (response) => response.session, }, createPayload: {}, workspaceStage: 'match3d-agent-workspace', resultStage: 'match3d-result', platformStage: 'platform', isCompileAction: (payload) => payload.action === 'match3d_compile_draft', resolveErrorMessage: resolveMatch3DErrorMessage, errorMessages: { open: '开启抓大鹅共创工作台失败。', restoreMissingSession: '这份抓大鹅草稿缺少会话信息,请重新开始创作。', restore: '读取抓大鹅创作草稿失败。', submit: '发送抓大鹅共创消息失败。', execute: '执行抓大鹅操作失败。', }, enterCreateTab, setSelectionStage, onSessionOpened: () => { setShowCreationTypeModal(false); }, onActionComplete: async ({ payload, response, setSession }) => { setSession(response.session); if (payload.action !== 'match3d_compile_draft') { return; } const profileId = response.session.draft?.profileId; if (!profileId) { setMatch3DProfile(null); return; } try { const { item } = await getMatch3DWorkDetail(profileId); setMatch3DProfile(item); await refreshMatch3DShelf().catch(() => undefined); } catch { setMatch3DProfile(buildMatch3DProfileFromSession(response.session)); } }, }); const puzzleFlow = usePlatformCreationAgentFlowController< PuzzleAgentSessionSnapshot, CreatePuzzleAgentSessionRequest, { session: PuzzleAgentSessionSnapshot }, SendPuzzleAgentMessageRequest, PuzzleAgentActionRequest, { operation: PuzzleAgentOperationRecord; session: PuzzleAgentSessionSnapshot; } >({ client: { createSession: createPuzzleAgentSession, getSession: getPuzzleAgentSession, streamMessage: streamPuzzleAgentMessage, executeAction: executePuzzleAgentAction, selectSession: (response) => response.session, }, createPayload: {}, workspaceStage: 'puzzle-agent-workspace', resultStage: 'puzzle-result', platformStage: 'platform', isCompileAction: (payload) => payload.action === 'compile_puzzle_draft', resolveErrorMessage: resolvePuzzleErrorMessage, errorMessages: { open: '开启拼图共创工作台失败。', restoreMissingSession: '这份拼图草稿缺少会话信息,请重新开始创作。', restore: '读取拼图创作草稿失败。', submit: '发送拼图共创消息失败。', execute: '执行拼图操作失败。', }, enterCreateTab, setSelectionStage, onSessionOpened: () => { setShowCreationTypeModal(false); }, onActionComplete: async ({ payload, response, setSession }) => { setPuzzleOperation(response.operation); setSession(response.session); const formPayload = buildPuzzleFormPayloadFromAction(payload); if (formPayload) { setPuzzleFormDraftPayload(formPayload); } if (payload.action === 'publish_puzzle_work') { await Promise.allSettled([ refreshPuzzleShelf(), refreshPuzzleGallery(), ]); } if (payload.action === 'compile_puzzle_draft') { setPuzzleGenerationState((current) => current ? { ...current, phase: 'ready', completedAssetCount: 1, totalAssetCount: 1, } : current, ); } if ( payload.action === 'publish_puzzle_work' && response.session.publishedProfileId ) { const galleryDetail = await getPuzzleGalleryDetail( response.session.publishedProfileId, ); setSelectedPuzzleDetail(galleryDetail.item); const detailEntry = mapPuzzleWorkToPublicWorkDetail(galleryDetail.item); setSelectedPublicWorkDetail(detailEntry); setPublicWorkDetailError(null); setSelectionStage('work-detail'); pushAppHistoryPath( buildPublicWorkStagePath( 'work-detail', buildPuzzlePublicWorkCode(galleryDetail.item.profileId), ), ); openPublishShareModal({ title: galleryDetail.item.workTitle || galleryDetail.item.levelName, publicWorkCode: buildPuzzlePublicWorkCode(galleryDetail.item.profileId), stage: 'puzzle-gallery-detail', }); } }, beforeExecuteAction: ({ payload }) => { const formPayload = buildPuzzleFormPayloadFromAction(payload); if (formPayload) { setPuzzleFormDraftPayload(formPayload); } 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; const bigFishError = bigFishFlow.error; const setBigFishError = bigFishFlow.setError; const isBigFishBusy = bigFishFlow.isBusy; const streamingBigFishReplyText = bigFishFlow.streamingReplyText; const isStreamingBigFishReply = bigFishFlow.isStreamingReply; const match3dSession = match3dFlow.session; const match3dError = match3dFlow.error; const setMatch3DSession = match3dFlow.setSession; const setMatch3DError = match3dFlow.setError; const isMatch3DBusy = match3dFlow.isBusy; const streamingMatch3DReplyText = match3dFlow.streamingReplyText; const setStreamingMatch3DReplyText = match3dFlow.setStreamingReplyText; const isStreamingMatch3DReply = match3dFlow.isStreamingReply; const setIsStreamingMatch3DReply = match3dFlow.setIsStreamingReply; const puzzleSession = puzzleFlow.session; const puzzleError = puzzleFlow.error; const setPuzzleError = puzzleFlow.setError; const isPuzzleBusy = puzzleFlow.isBusy; const setIsPuzzleBusy = puzzleFlow.setIsBusy; const isStreamingPuzzleReply = puzzleFlow.isStreamingReply; const resetRpgSessionViewState = sessionController.resetSessionViewState; const setRpgGeneratedCustomWorldProfile = sessionController.setGeneratedCustomWorldProfile; const setRpgCustomWorldError = sessionController.setCustomWorldError; const persistRpgAgentUiState = sessionController.persistAgentUiState; const resetAutoSaveTrackingToIdle = autosaveCoordinator.resetAutoSaveTrackingToIdle; useEffect(() => { puzzleRunRef.current = puzzleRun; }, [puzzleRun]); const openBigFishAgentWorkspace = useCallback(async () => { setBigFishRun(null); await bigFishFlow.openWorkspace(); }, [bigFishFlow]); const openMatch3DAgentWorkspace = useCallback(async () => { setMatch3DSession(null); setMatch3DProfile(null); setMatch3DRun(null); setMatch3DError(null); setStreamingMatch3DReplyText(''); setIsStreamingMatch3DReply(false); await match3dFlow.openWorkspace(); }, [ match3dFlow, setIsStreamingMatch3DReply, setMatch3DError, setMatch3DProfile, setMatch3DRun, setMatch3DSession, setStreamingMatch3DReplyText, ]); const openPuzzleAgentWorkspace = useCallback(async () => { setPuzzleRun(null); setPuzzleOperation(null); setPuzzleGenerationState(null); setPuzzleFormDraftPayload(null); const nextSession = await puzzleFlow.openWorkspace({}); if (nextSession) { void refreshPuzzleShelf(); } }, [puzzleFlow, refreshPuzzleShelf]); const createPuzzleDraftFromForm = useCallback( async (payload: CreatePuzzleAgentSessionRequest) => { setPuzzleFormDraftPayload(payload); const nextSession = puzzleFlow.session && !isEmptyPuzzleFormOnlyDraft(puzzleFlow.session) ? puzzleFlow.session : await puzzleFlow.openWorkspace(payload); if (!nextSession) { return; } await puzzleFlow.executeAction( buildPuzzleCompileActionFromFormPayload(payload), nextSession, ); }, [puzzleFlow], ); const savePuzzleFormDraft = useCallback( async (payload: CreatePuzzleAgentSessionRequest) => { const session = puzzleFlow.session; if (!session || session.stage !== 'collecting_anchors') { return; } setPuzzleFormDraftPayload(payload); try { const response = await executePuzzleAgentAction(session.sessionId, { action: 'save_puzzle_form_draft', promptText: payload.pictureDescription ?? null, workTitle: payload.workTitle ?? payload.seedText ?? '', workDescription: payload.workDescription ?? '', pictureDescription: payload.pictureDescription ?? '', imageModel: payload.imageModel ?? null, }); setPuzzleOperation(response.operation); puzzleFlow.setSession(response.session); setPuzzleError(null); void refreshPuzzleShelf(); } catch (error) { setPuzzleError( resolvePuzzleErrorMessage(error, '保存拼图表单草稿失败。'), ); } }, [puzzleFlow, refreshPuzzleShelf, resolvePuzzleErrorMessage, setPuzzleError], ); useEffect(() => { if (platformBootstrap.canReadProtectedData) { hadReadableProtectedDataRef.current = true; return; } if (authUi?.user || !hadReadableProtectedDataRef.current) { return; } hadReadableProtectedDataRef.current = false; // 创作中心只展示当前登录用户的私有作品。 // 一旦退出登录或鉴权上下文被收回,三类作品缓存必须同步清空,不能等刷新页面。 setShowCreationTypeModal(false); setSelectedDetailEntry(null); setSelectedPublicWorkDetail(null); setPublicWorkDetailError(null); setIsPublicWorkDetailBusy(false); setBigFishWorks([]); setBigFishRun(null); setBigFishRuntimeShare(null); setBigFishRuntimeWork(null); setBigFishRuntimeStartedAt(null); setBigFishRuntimeReturnStage('platform'); setBigFishGenerationState(null); setBigFishError(null); setMatch3DSession(null); setMatch3DProfile(null); setMatch3DWorks([]); setMatch3DGalleryEntries([]); setMatch3DRun(null); setMatch3DRuntimeReturnStage('match3d-result'); setMatch3DError(null); setStreamingMatch3DReplyText(''); setIsStreamingMatch3DReply(false); setPuzzleOperation(null); setPuzzleWorks([]); setSelectedPuzzleDetail(null); setPuzzleRuntimeReturnStage('puzzle-gallery-detail'); setPuzzleRun(null); setPuzzleGenerationState(null); setIsPuzzleNextLevelGenerating(false); setPuzzleError(null); setDeletingCreationWorkId(null); setClaimingPuzzlePointIncentiveProfileId(null); setPublishSharePayload(null); setProfilePlayStats(null); setProfilePlayStatsError(null); setIsProfilePlayStatsOpen(false); resetRpgSessionViewState(); setRpgGeneratedCustomWorldProfile(null); setRpgCustomWorldError(null); persistRpgAgentUiState(null, null); resetAutoSaveTrackingToIdle(); if ( selectionStage !== 'platform' && selectionStage !== 'work-detail' && selectionStage !== 'detail' && selectionStage !== 'puzzle-gallery-detail' ) { setSelectionStage('platform'); } }, [ authUi?.user, platformBootstrap.canReadProtectedData, persistRpgAgentUiState, resetAutoSaveTrackingToIdle, resetRpgSessionViewState, selectionStage, setBigFishError, setIsStreamingMatch3DReply, setMatch3DError, setMatch3DSession, setPuzzleError, setRpgCustomWorldError, setRpgGeneratedCustomWorldProfile, setSelectionStage, setStreamingMatch3DReplyText, ]); const handleCreationHubCreateType = useCallback( (type: PlatformCreationTypeId) => { if (type === 'airp' || type === 'visual-novel') { return; } if (!prepareCreationLaunch()) { return; } if (type === 'rpg') { runProtectedAction(() => { void sessionController.openRpgAgentWorkspace(); }); return; } if (type === 'big-fish') { runProtectedAction(() => { void openBigFishAgentWorkspace(); }); return; } if (type === 'match3d') { runProtectedAction(() => { void openMatch3DAgentWorkspace(); }); return; } if (type === 'puzzle') { runProtectedAction(() => { void openPuzzleAgentWorkspace(); }); } }, [ openBigFishAgentWorkspace, openMatch3DAgentWorkspace, openPuzzleAgentWorkspace, prepareCreationLaunch, runProtectedAction, sessionController, ], ); const leaveBigFishFlow = useCallback(() => { setBigFishRun(null); setBigFishRuntimeWork(null); setBigFishRuntimeStartedAt(null); setBigFishRuntimeReturnStage('platform'); setBigFishGenerationState(null); bigFishFlow.leaveFlow(); }, [bigFishFlow]); const leaveMatch3DFlow = useCallback(() => { setMatch3DRun(null); setMatch3DRuntimeReturnStage('match3d-result'); match3dFlow.leaveFlow(); }, [match3dFlow]); const leavePuzzleFlow = useCallback(() => { setPuzzleOperation(null); setPuzzleRun(null); setPuzzleGenerationState(null); setIsPuzzleNextLevelGenerating(false); puzzleFlow.leaveFlow(); }, [puzzleFlow]); const submitBigFishMessage = bigFishFlow.submitMessage; const submitMatch3DMessage = match3dFlow.submitMessage; const submitPuzzleMessage = puzzleFlow.submitMessage; const executeBigFishAction = bigFishFlow.executeAction; const executeMatch3DAction = match3dFlow.executeAction; const executePuzzleAction = puzzleFlow.executeAction; const retryPuzzleDraftGeneration = useCallback(() => { if (puzzleFormDraftPayload) { void createPuzzleDraftFromForm(puzzleFormDraftPayload); return; } void executePuzzleAction( buildPuzzleCompileActionFromFormPayload(puzzleFormDraftPayload), ); }, [createPuzzleDraftFromForm, executePuzzleAction, puzzleFormDraftPayload]); const executePuzzleWorkspaceAction = useCallback( (payload: PuzzleAgentActionRequest) => { if ( payload.action === 'compile_puzzle_draft' && isEmptyPuzzleFormOnlyDraft(puzzleFlow.session) ) { const formPayload = buildPuzzleFormPayloadFromAction(payload); if (formPayload) { void createPuzzleDraftFromForm(formPayload); return; } } void executePuzzleAction(payload); }, [createPuzzleDraftFromForm, executePuzzleAction, puzzleFlow.session], ); useEffect(() => { if (selectionStage === 'big-fish-result' && !bigFishSession?.draft) { setSelectionStage( bigFishSession ? 'big-fish-agent-workspace' : 'platform', ); } if (selectionStage === 'big-fish-runtime' && !bigFishRun) { setSelectionStage(bigFishSession?.draft ? 'big-fish-result' : 'platform'); } }, [bigFishRun, bigFishSession, selectionStage, setSelectionStage]); useEffect(() => { if (selectionStage === 'match3d-result' && !match3dSession?.draft) { setSelectionStage( match3dSession ? 'match3d-agent-workspace' : 'platform', ); } if (selectionStage === 'match3d-runtime' && !match3dRun) { setSelectionStage(match3dSession?.draft ? 'match3d-result' : 'platform'); } }, [match3dRun, match3dSession, selectionStage, setSelectionStage]); const startBigFishRun = useCallback(() => { if (!bigFishSession) { return; } const sessionId = bigFishSession.sessionId; setBigFishError(null); setBigFishRuntimeShare(null); setBigFishRuntimeWork(null); setBigFishRuntimeStartedAt(Date.now()); setBigFishRuntimeReturnStage('big-fish-result'); setBigFishRun(startLocalBigFishRuntimeRun({ session: bigFishSession })); setSelectionStage('big-fish-runtime'); void recordBigFishPlay(sessionId, { elapsedMs: 0 }).catch((error) => { setBigFishError( resolveBigFishErrorMessage(error, '记录大鱼吃小鱼游玩失败。'), ); }); void refreshBigFishShelf(); }, [ bigFishSession, refreshBigFishShelf, resolveBigFishErrorMessage, setSelectionStage, ]); const restartBigFishRun = useCallback(() => { if (!bigFishSession && !bigFishRun) { return; } const sessionId = bigFishSession?.sessionId ?? bigFishRun?.sessionId; if (!sessionId) { return; } setBigFishError(null); if (bigFishSession) { setBigFishRuntimeShare(null); setBigFishRuntimeReturnStage('big-fish-result'); } setBigFishRuntimeStartedAt(Date.now()); setBigFishRun( startLocalBigFishRuntimeRun({ session: bigFishSession, work: bigFishRuntimeWork, }), ); setSelectionStage('big-fish-runtime'); void recordBigFishPlay(sessionId, { elapsedMs: 0 }).catch((error) => { setBigFishError( resolveBigFishErrorMessage(error, '记录大鱼吃小鱼游玩失败。'), ); }); }, [ bigFishRun, bigFishRuntimeWork, bigFishSession, resolveBigFishErrorMessage, setSelectionStage, ]); const startPuzzleRunFromProfile = useCallback( async ( profileId: string, returnStage: PuzzleRuntimeReturnStage = 'work-detail', detailItem?: PuzzleWorkSummary, mirrorErrorToPublicDetail = false, levelId?: string | null, ) => { if (isPuzzleBusy) { return; } setIsPuzzleBusy(true); setPuzzleError(null); try { const item = detailItem ?? (await getPuzzleGalleryDetail(profileId)).item; const { run } = await startPuzzleRun({ profileId: item.profileId, levelId: levelId ?? null, }); setSelectedPuzzleDetail(item); setPuzzleRun(run); setPuzzleRuntimeReturnStage(returnStage); setSelectionStage('puzzle-runtime'); void platformBootstrap.refreshSaveArchives(); pushAppHistoryPath( buildPublicWorkStagePath( 'puzzle-runtime', buildPuzzlePublicWorkCode(item.profileId), ), ); } catch (error) { const message = resolvePuzzleErrorMessage(error, '启动拼图玩法失败。'); setPuzzleError(message); if (mirrorErrorToPublicDetail) { setPublicWorkDetailError(message); } } finally { setIsPuzzleBusy(false); } }, [ isPuzzleBusy, platformBootstrap, resolvePuzzleErrorMessage, setIsPuzzleBusy, setPuzzleError, setSelectionStage, ], ); const startMatch3DRunFromProfile = useCallback( async ( profile: Match3DWorkProfile | Match3DWorkSummary, returnStage: 'match3d-result' | 'work-detail' = 'match3d-result', mirrorErrorToPublicDetail = false, ) => { if (isMatch3DBusy) { return; } match3dFlow.setIsBusy(true); setMatch3DError(null); try { const { run } = await startMatch3DRun(profile.profileId); setMatch3DRun(run); setMatch3DRuntimeReturnStage(returnStage); setSelectionStage('match3d-runtime'); if (profile.publicationStatus === 'published') { pushAppHistoryPath( buildPublicWorkStagePath( 'work-detail', buildMatch3DPublicWorkCode(profile.profileId), ), ); } } catch (error) { const message = resolveMatch3DErrorMessage( error, '启动抓大鹅玩法失败。', ); setMatch3DError(message); if (mirrorErrorToPublicDetail) { setPublicWorkDetailError(message); } } finally { match3dFlow.setIsBusy(false); } }, [ isMatch3DBusy, match3dFlow, resolveMatch3DErrorMessage, setMatch3DError, setSelectionStage, ], ); const buildPuzzleTestWork = useCallback( (draft: PuzzleResultDraft) => { const profileId = puzzleSession?.publishedProfileId ?? `draft-${puzzleSession?.sessionId ?? 'puzzle'}-test`; const now = new Date().toISOString(); return { workId: `test-${profileId}`, profileId, ownerUserId: authUi?.user?.id ?? 'current-user', sourceSessionId: puzzleSession?.sessionId ?? null, authorDisplayName: authUi?.user?.displayName ?? '玩家', workTitle: draft.workTitle || draft.levelName, workDescription: draft.workDescription || draft.summary, levelName: draft.levelName, summary: draft.summary, themeTags: draft.themeTags, coverImageSrc: draft.coverImageSrc, coverAssetId: draft.coverAssetId, publicationStatus: 'draft', updatedAt: now, publishedAt: null, playCount: 0, remixCount: 0, likeCount: 0, pointIncentiveTotalHalfPoints: 0, pointIncentiveClaimedPoints: 0, pointIncentiveTotalPoints: 0, pointIncentiveClaimablePoints: 0, publishReady: Boolean(puzzleSession?.resultPreview?.publishReady), levels: draft.levels, } satisfies PuzzleWorkSummary; }, [ authUi?.user?.displayName, authUi?.user?.id, puzzleSession?.publishedProfileId, puzzleSession?.resultPreview?.publishReady, puzzleSession?.sessionId, ], ); const startPuzzleTestRunFromDraft = useCallback( (draft: PuzzleResultDraft) => { if (!draft.coverImageSrc) { setPuzzleError('请先选择一张正式拼图图片。'); return; } const testWork = buildPuzzleTestWork(draft); setSelectedPuzzleDetail(testWork); setPuzzleRun(startLocalPuzzleRun(testWork)); setPuzzleRuntimeReturnStage('puzzle-result'); setPuzzleError(null); setSelectionStage('puzzle-runtime'); }, [buildPuzzleTestWork, setSelectionStage], ); const submitBigFishInput = useCallback( (payload: SubmitBigFishInputRequest) => { if (!bigFishRun || bigFishRun.status !== 'running') { return; } setBigFishRun((currentRun) => currentRun ? advanceLocalBigFishRuntimeRun(currentRun, payload) : currentRun, ); }, [bigFishRun], ); const reportBigFishObservedPlayTime = useCallback(() => { const sessionId = bigFishRun?.sessionId?.trim(); if (!sessionId || !bigFishRuntimeStartedAt) { return; } const elapsedMs = Math.max(1_000, Date.now() - bigFishRuntimeStartedAt); setBigFishRuntimeStartedAt(null); void recordBigFishPlay(sessionId, { elapsedMs }).catch((error) => { setBigFishError( resolveBigFishErrorMessage(error, '记录大鱼吃小鱼游玩时长失败。'), ); }); }, [ bigFishRun?.sessionId, bigFishRuntimeStartedAt, resolveBigFishErrorMessage, ]); const swapPuzzlePiecesInRun = useCallback( (payload: { firstPieceId: string; secondPieceId: string }) => { if (!puzzleRun || isPuzzleBusy) { return; } setPuzzleError(null); // 交换、合并与通关判定都由前端即时裁决,正式 run 不再等待后端 /swap。 setPuzzleRun(swapLocalPuzzlePieces(puzzleRun, payload)); }, [isPuzzleBusy, puzzleRun, setPuzzleError], ); const dragPuzzlePiece = useCallback( (payload: { pieceId: string; targetRow: number; targetCol: number }) => { if (!puzzleRun || isPuzzleBusy) { return; } setPuzzleError(null); // 拖动落点、合并、拆分与通关判定都属于前端即时交互裁决。 // 后端只保留开局、道具、下一关与真实排行榜等服务侧能力。 setPuzzleRun(dragLocalPuzzlePiece(puzzleRun, payload)); }, [isPuzzleBusy, puzzleRun, setPuzzleError], ); useEffect(() => { if (selectionStage !== 'puzzle-runtime' || !puzzleRun?.currentLevel) { return; } if (puzzleRun.currentLevel.status !== 'playing') { return; } const timerId = window.setInterval(() => { // 中文注释:正式 run 的棋盘交互也在前端即时裁决,倒计时展示同样走本地时钟;超时落库仍由 onTimeExpired 拉取后端快照完成。 setPuzzleRun((currentRun) => currentRun ? refreshLocalPuzzleTimer(currentRun) : currentRun, ); }, 250); return () => window.clearInterval(timerId); }, [puzzleRun, selectionStage]); const setPuzzleRuntimePaused = useCallback( async (paused: boolean) => { if (!puzzleRun?.currentLevel) { return; } if (isLocalPuzzleRun(puzzleRun)) { setPuzzleRun((currentRun) => currentRun ? setLocalPuzzlePaused(currentRun, paused) : currentRun, ); return; } try { const { run } = await updatePuzzleRunPause(puzzleRun.runId, { paused, }); setPuzzleRun((currentRun) => currentRun ? mergePuzzleServiceRuntimeState(currentRun, run) : currentRun, ); void platformBootstrap.refreshProfileDashboard(); } catch (error) { setPuzzleError( resolvePuzzleErrorMessage(error, '更新拼图计时状态失败。'), ); } }, [platformBootstrap, puzzleRun, resolvePuzzleErrorMessage, setPuzzleError], ); const syncPuzzleRuntimeTimeout = useCallback(async () => { if ( !puzzleRun?.currentLevel || puzzleRun.currentLevel.status === 'cleared' ) { return; } if (isLocalPuzzleRun(puzzleRun)) { setPuzzleRun((currentRun) => currentRun ? refreshLocalPuzzleTimer(currentRun) : currentRun, ); return; } try { const { run } = await getPuzzleRun(puzzleRun.runId); setPuzzleRun((currentRun) => currentRun ? mergePuzzleServiceRuntimeState(currentRun, run) : currentRun, ); void platformBootstrap.refreshSaveArchives(); } catch (error) { setPuzzleError( resolvePuzzleErrorMessage(error, '同步拼图失败状态失败。'), ); } }, [platformBootstrap, puzzleRun, resolvePuzzleErrorMessage, setPuzzleError]); const usePuzzleProp = useCallback( async (propKind: PuzzleRuntimePropKind) => { if (!puzzleRun?.currentLevel) { return null; } const canUseProp = propKind === 'extendTime' ? puzzleRun.currentLevel.status !== 'cleared' : puzzleRun.currentLevel.status === 'playing'; if (!canUseProp) { return null; } if (isLocalPuzzleRun(puzzleRun)) { const currentRun = puzzleRunRef.current ?? puzzleRun; if (!currentRun.currentLevel) { return null; } const nextRun = propKind === 'extendTime' ? extendLocalPuzzleTime(currentRun) : propKind === 'freezeTime' ? applyLocalPuzzleFreezeTime(currentRun) : setLocalPuzzlePaused(currentRun, propKind === 'reference'); puzzleRunRef.current = nextRun; setPuzzleRun(nextRun); return nextRun; } const { run } = await consumePuzzleRuntimeProp(puzzleRun.runId, { propKind, }); const nextRun = mergePuzzleServiceRuntimeState( puzzleRunRef.current ?? puzzleRun, run, ); puzzleRunRef.current = nextRun; setPuzzleRun(nextRun); void platformBootstrap.refreshProfileDashboard(); void platformBootstrap.refreshSaveArchives(); return nextRun; }, [platformBootstrap, puzzleRun], ); const restartPuzzleCurrentLevel = useCallback(async () => { const currentLevel = puzzleRun?.currentLevel ?? null; if (!puzzleRun || !currentLevel || isPuzzleBusy) { return; } setPuzzleError(null); const restartLevelId = resolvePuzzleRestartLevelId( puzzleRun, selectedPuzzleDetail, ); if (isLocalPuzzleRun(puzzleRun)) { const nextRun = restartLocalPuzzleLevel( puzzleRunRef.current ?? puzzleRun, ); puzzleRunRef.current = nextRun; setPuzzleRun(nextRun); return; } await startPuzzleRunFromProfile( currentLevel.profileId, puzzleRuntimeReturnStage, selectedPuzzleDetail?.profileId === currentLevel.profileId ? selectedPuzzleDetail : undefined, false, restartLevelId, ); }, [ isPuzzleBusy, puzzleRun, puzzleRuntimeReturnStage, selectedPuzzleDetail, setPuzzleError, startPuzzleRunFromProfile, ]); const resumePuzzleSaveArchive = useCallback( async (entry: ProfileSaveArchiveSummary) => { if (isPuzzleBusy) { return; } setIsPuzzleBusy(true); setPuzzleError(null); platformBootstrap.setSaveError(null); try { const resumedArchive = await resumePuzzleProfileSaveArchiveRaw( entry.worldKey, ); platformBootstrap.setSaveEntries((currentEntries) => currentEntries.map((currentEntry) => currentEntry.worldKey === resumedArchive.entry.worldKey ? resumedArchive.entry : currentEntry, ), ); const gameState = resumedArchive.snapshot.gameState; const profileId = typeof gameState.currentProfileId === 'string' && gameState.currentProfileId.trim() ? gameState.currentProfileId : typeof gameState.entryProfileId === 'string' && gameState.entryProfileId.trim() ? gameState.entryProfileId : (entry.profileId ?? entry.worldKey.replace(/^puzzle:/u, '')); const levelId = typeof gameState.currentLevelId === 'string' && gameState.currentLevelId.trim() ? gameState.currentLevelId : null; await startPuzzleRunFromProfile( profileId, 'platform', undefined, false, levelId, ); } catch (error) { platformBootstrap.setSaveError( resolvePuzzleErrorMessage(error, '恢复拼图存档失败。'), ); } finally { setIsPuzzleBusy(false); } }, [ isPuzzleBusy, platformBootstrap, resolvePuzzleErrorMessage, setPuzzleError, startPuzzleRunFromProfile, ], ); useEffect(() => { const currentLevel = puzzleRun?.currentLevel ?? null; if (!puzzleRun || !currentLevel || currentLevel.status !== 'cleared') { return; } if (currentLevel.elapsedMs === null) { return; } if ((currentLevel.leaderboardEntries ?? []).length > 0) { return; } const submitKey = `${puzzleRun.runId}:${currentLevel.profileId}:${currentLevel.gridSize}:${currentLevel.elapsedMs}`; if (submittedPuzzleLeaderboardKeysRef.current.has(submitKey)) { return; } submittedPuzzleLeaderboardKeysRef.current.add(submitKey); setIsPuzzleLeaderboardBusy(true); const payload: SubmitPuzzleLeaderboardRequest = { profileId: currentLevel.profileId, gridSize: currentLevel.gridSize, elapsedMs: currentLevel.elapsedMs, nickname: authUi?.user?.displayName?.trim() || '玩家', }; if (isLocalPuzzleRun(puzzleRun)) { setPuzzleRun(submitLocalPuzzleLeaderboard(puzzleRun, payload.nickname)); void advanceLocalPuzzleNextLevel({ run: puzzleRun, sourceSessionId: selectedPuzzleDetail?.sourceSessionId ?? puzzleSession?.sessionId ?? null, }) .then(({ run }) => { setPuzzleRun((currentRun) => { if (!currentRun) { return currentRun; } return mergePuzzleServiceRuntimeState(currentRun, run); }); }) .catch(() => { // 中文注释:本地试玩缺少后端候选时保留本地排行榜和既有下一关入口,避免结算被探测请求打断。 }) .finally(() => { setIsPuzzleLeaderboardBusy(false); }); return; } void submitPuzzleLeaderboard(puzzleRun.runId, payload) .then(({ run }) => { setPuzzleRun((currentRun) => { if (!currentRun) { return currentRun; } return mergePuzzleServiceRuntimeState(currentRun, run); }); void platformBootstrap.refreshSaveArchives(); }) .catch((error) => { submittedPuzzleLeaderboardKeysRef.current.delete(submitKey); setPuzzleError( resolvePuzzleErrorMessage(error, '提交拼图排行榜失败。'), ); }) .finally(() => { setIsPuzzleLeaderboardBusy(false); }); }, [ authUi?.user?.displayName, platformBootstrap, puzzleRun, puzzleSession, resolvePuzzleErrorMessage, selectedPuzzleDetail, setPuzzleError, ]); const advancePuzzleLevel = useCallback( async (target?: { profileId?: string; levelId?: string | null }) => { if (!puzzleRun || isPuzzleBusy || isPuzzleLeaderboardBusy) { return; } const currentLevel = puzzleRun.currentLevel; if (!currentLevel || currentLevel.status !== 'cleared') { return; } setIsPuzzleBusy(true); setIsPuzzleNextLevelGenerating(true); setPuzzleError(null); try { const targetProfileId = target?.profileId?.trim(); if ( targetProfileId && targetProfileId !== currentLevel.profileId && puzzleRun.nextLevelMode === 'similarWorks' ) { await startPuzzleRunFromProfile( targetProfileId, 'puzzle-gallery-detail', undefined, false, null, ); return; } const { run } = isLocalPuzzleRun(puzzleRun) ? await advanceLocalPuzzleNextLevel({ run: puzzleRun, sourceSessionId: selectedPuzzleDetail?.sourceSessionId ?? puzzleSession?.sessionId ?? null, }) : await advancePuzzleNextLevel(puzzleRun.runId); setPuzzleRun(run); if (!isLocalPuzzleRun(puzzleRun)) { void platformBootstrap.refreshSaveArchives(); } } catch (error) { setPuzzleError(resolvePuzzleErrorMessage(error, '准备下一关失败。')); } finally { setIsPuzzleNextLevelGenerating(false); setIsPuzzleBusy(false); } }, [ isPuzzleBusy, isPuzzleLeaderboardBusy, platformBootstrap, puzzleRun, puzzleSession, resolvePuzzleErrorMessage, selectedPuzzleDetail, startPuzzleRunFromProfile, ], ); const remodelCurrentPuzzleRuntimeWork = useCallback((profileId: string) => { const targetProfileId = profileId.trim(); if (!targetProfileId || isPublicWorkDetailBusy || isPuzzleBusy) { return; } runProtectedAction(() => { setIsPublicWorkDetailBusy(true); setIsPuzzleBusy(true); setPuzzleError(null); setPublicWorkDetailError(null); void remixPuzzleGalleryWork(targetProfileId) .then((response) => { puzzleFlow.setSession(response.session); setPuzzleOperation(null); setPuzzleRun(null); enterCreateTab(); setSelectionStage('puzzle-result'); }) .catch((error) => { setPuzzleError(resolvePuzzleErrorMessage(error, '改造拼图作品失败。')); }) .finally(() => { setIsPublicWorkDetailBusy(false); setIsPuzzleBusy(false); }); }); }, [ enterCreateTab, isPublicWorkDetailBusy, isPuzzleBusy, puzzleFlow, resolvePuzzleErrorMessage, runProtectedAction, setIsPuzzleBusy, setPuzzleError, setSelectionStage, ]); const leaveAgentWorkspace = useCallback(() => { enterCreateTab(); sessionController.resetSessionViewState(); sessionController.setGeneratedCustomWorldProfile(null); autosaveCoordinator.resetAutoSaveTrackingToIdle(); sessionController.persistAgentUiState( sessionController.activeAgentSessionId, null, ); setSelectionStage('platform'); }, [ autosaveCoordinator, enterCreateTab, sessionController, setSelectionStage, ]); const leaveAgentDraftGeneration = useCallback(() => { if (sessionController.isActiveGenerationRunning) { return; } sessionController.setAgentDraftGenerationStartedAt(null); sessionController.setCustomWorldGenerationViewSource(null); setSelectionStage('agent-workspace'); }, [sessionController, setSelectionStage]); const leaveAgentDraftResult = useCallback(() => { sessionController.suppressAgentDraftResultAutoOpen(); sessionController.setGeneratedCustomWorldProfile(null); sessionController.setCustomWorldError(null); autosaveCoordinator.resetAutoSaveTrackingToIdle(); sessionController.setCustomWorldGenerationViewSource(null); sessionController.setCustomWorldResultViewSource(null); enterCreateTab(); setSelectionStage('platform'); }, [ autosaveCoordinator, enterCreateTab, sessionController, setSelectionStage, ]); const leaveCustomWorldResult = useCallback(() => { sessionController.setGeneratedCustomWorldProfile(null); sessionController.setCustomWorldError(null); autosaveCoordinator.resetAutoSaveTrackingToIdle(); sessionController.setCustomWorldGenerationViewSource(null); sessionController.setCustomWorldResultViewSource(null); setSelectionStage(selectedDetailEntry ? 'detail' : 'platform'); }, [ autosaveCoordinator, selectedDetailEntry, sessionController, setSelectionStage, ]); const handleStartSelectedWorld = useCallback(() => { if (!selectedDetailEntry) { return; } runProtectedAction(() => { handleCustomWorldSelect(selectedDetailEntry.profile); }); }, [handleCustomWorldSelect, runProtectedAction, selectedDetailEntry]); const handleDeleteLibraryEntry = useCallback( (entry: CustomWorldLibraryEntry) => { if (!entry.profileId || deletingCreationWorkId) { return; } requestDeleteCreationWork({ id: entry.profileId, title: entry.worldName, detail: '删除后会从你的作品列表和公开广场中移除。', run: () => { setDeletingCreationWorkId(entry.profileId); platformBootstrap.setPlatformError(null); void deleteRpgEntryWorldProfile(entry.profileId) .then(async (entries) => { platformBootstrap.setSavedCustomWorldEntries(entries); await platformBootstrap.refreshCustomWorldWorks().catch(() => []); await platformBootstrap.refreshPublishedGallery().catch(() => []); }) .catch((error) => { platformBootstrap.setPlatformError( resolveRpgCreationErrorMessage(error, '删除自定义世界失败。'), ); }) .finally(() => { setDeletingCreationWorkId(null); }); }, }); }, [deletingCreationWorkId, platformBootstrap, requestDeleteCreationWork], ); const handleDeletePublishedWork = useCallback( (work: (typeof creationHubItems)[number]) => { if (deletingCreationWorkId) { return; } requestDeleteCreationWork({ id: work.workId, title: work.title, detail: work.status === 'published' ? '删除后会从你的作品列表和公开广场中移除。' : '删除后会从你的作品列表中移除。', run: () => { setDeletingCreationWorkId(work.workId); platformBootstrap.setPlatformError(null); const deleteTask = work.sourceType === 'published_profile' && work.profileId ? deleteRpgEntryWorldProfile(work.profileId).then( async (entries) => { platformBootstrap.setSavedCustomWorldEntries(entries); await platformBootstrap .refreshCustomWorldWorks() .catch(() => []); }, ) : work.sourceType === 'agent_session' && work.sessionId ? deleteRpgCreationAgentSession(work.sessionId).then( (items) => { platformBootstrap.setCustomWorldWorkEntries(items); }, ) : Promise.reject(new Error('当前 RPG 作品缺少可删除 ID。')); void deleteTask .then(async () => { await platformBootstrap.refreshPublishedGallery().catch(() => []); }) .catch((error) => { platformBootstrap.setPlatformError( resolveRpgCreationErrorMessage(error, '删除自定义世界失败。'), ); }) .finally(() => { setDeletingCreationWorkId(null); }); }, }); }, [deletingCreationWorkId, platformBootstrap, requestDeleteCreationWork], ); const handleDeleteBigFishWork = useCallback( (work: BigFishWorkSummary) => { if (deletingCreationWorkId) { return; } requestDeleteCreationWork({ id: work.workId, title: work.title, detail: work.status === 'published' ? '删除后会从你的作品列表和公开广场中移除。' : '删除后会从你的作品列表中移除。', run: () => { setDeletingCreationWorkId(work.workId); setBigFishError(null); void deleteBigFishWork(work.sourceSessionId) .then(async (response) => { setBigFishWorks(response.items); await refreshBigFishGallery().catch(() => []); }) .catch((error) => { setBigFishError( resolveBigFishErrorMessage(error, '删除大鱼吃小鱼作品失败。'), ); }) .finally(() => { setDeletingCreationWorkId(null); }); }, }); }, [ deletingCreationWorkId, refreshBigFishGallery, requestDeleteCreationWork, resolveBigFishErrorMessage, setBigFishError, ], ); const handleDeletePuzzleWork = useCallback( (work: PuzzleWorkSummary) => { if (deletingCreationWorkId) { return; } const displayName = work.workTitle?.trim() || work.levelName.trim() || '未命名拼图'; requestDeleteCreationWork({ id: work.workId, title: displayName, detail: work.publicationStatus === 'published' ? '删除后会从你的作品列表和公开广场中移除。' : '删除后会从你的作品列表中移除。', run: () => { setDeletingCreationWorkId(work.workId); setPuzzleFormDraftPayload(null); setPuzzleError(null); void deletePuzzleWork(work.profileId) .then((response) => { setPuzzleWorks(response.items); void refreshPuzzleGallery(); }) .catch((error) => { setPuzzleError( resolvePuzzleErrorMessage(error, '删除拼图作品失败。'), ); }) .finally(() => { setDeletingCreationWorkId(null); }); }, }); }, [ deletingCreationWorkId, refreshPuzzleGallery, requestDeleteCreationWork, resolvePuzzleErrorMessage, setPuzzleError, ], ); const handleDeleteMatch3DWork = useCallback( (work: Match3DWorkSummary) => { if (deletingCreationWorkId) { return; } requestDeleteCreationWork({ id: work.workId, title: work.gameName, detail: work.publicationStatus === 'published' ? '删除后会从你的作品列表和公开广场中移除。' : '删除后会从你的作品列表中移除。', run: () => { setDeletingCreationWorkId(work.workId); setMatch3DError(null); void deleteMatch3DWork(work.profileId) .then((response) => { setMatch3DWorks(response.items); void refreshMatch3DGallery(); }) .catch((error) => { setMatch3DError( resolveMatch3DErrorMessage(error, '删除抓大鹅作品失败。'), ); }) .finally(() => { setDeletingCreationWorkId(null); }); }, }); }, [ deletingCreationWorkId, refreshMatch3DGallery, requestDeleteCreationWork, resolveMatch3DErrorMessage, setMatch3DError, ], ); const clearSelectedPublicWorkAuthor = useCallback(() => { publicWorkAuthorRequestKeyRef.current += 1; setSelectedPublicWorkAuthor(null); }, []); const loadSelectedPublicWorkAuthor = useCallback( (entry: PlatformPublicGalleryCard) => { const requestKey = publicWorkAuthorRequestKeyRef.current + 1; publicWorkAuthorRequestKeyRef.current = requestKey; setSelectedPublicWorkAuthor(null); void resolvePublicWorkAuthorSummary(entry) .then((author) => { if (publicWorkAuthorRequestKeyRef.current === requestKey) { setSelectedPublicWorkAuthor(author); } }) .catch(() => { if (publicWorkAuthorRequestKeyRef.current === requestKey) { setSelectedPublicWorkAuthor(null); } }); }, [], ); const openPublicWorkDetail = useCallback( (entry: PlatformPublicGalleryCard) => { setSelectedPublicWorkDetail(entry); setPublicWorkDetailError(null); setSelectionStage('work-detail'); if (entry.publicWorkCode?.trim()) { pushAppHistoryPath( buildPublicWorkStagePath('work-detail', entry.publicWorkCode), ); } }, [setSelectionStage], ); const syncUpdatedPublicWorkDetail = useCallback( (updatedEntry: PlatformPublicGalleryCard) => { setSelectedPublicWorkDetail((current) => current && isSamePlatformPublicGalleryEntry(current, updatedEntry) ? updatedEntry : current, ); }, [], ); const handleClaimPuzzlePointIncentive = useCallback( (work: PuzzleWorkSummary) => { if (claimingPuzzlePointIncentiveProfileId) { return; } runProtectedAction(() => { setClaimingPuzzlePointIncentiveProfileId(work.profileId); setPuzzleError(null); void claimPuzzleWorkPointIncentive(work.profileId) .then((response) => { const updatedWork = response.item; setPuzzleWorks((current) => current.map((item) => mergePuzzleWorkSummary(item, updatedWork)), ); setPuzzleGalleryEntries((current) => current.map((item) => mergePuzzleWorkSummary(item, updatedWork)), ); setSelectedPuzzleDetail((current) => current ? mergePuzzleWorkSummary(current, updatedWork) : current, ); syncUpdatedPublicWorkDetail( mapPuzzleWorkToPublicWorkDetail(updatedWork), ); void platformBootstrap.refreshProfileDashboard(); }) .catch((error) => { setPuzzleError( resolvePuzzleErrorMessage(error, '领取拼图积分激励失败。'), ); }) .finally(() => { setClaimingPuzzlePointIncentiveProfileId(null); }); }); }, [ claimingPuzzlePointIncentiveProfileId, platformBootstrap, resolvePuzzleErrorMessage, runProtectedAction, setPuzzleError, syncUpdatedPublicWorkDetail, ], ); const likePublicWork = useCallback( (entry: PlatformPublicGalleryCard) => { if (isPublicWorkDetailBusy) { return; } runProtectedAction(() => { setIsPublicWorkDetailBusy(true); setPublicWorkDetailError(null); if (isBigFishGalleryEntry(entry)) { void likeBigFishGalleryWork(entry.profileId) .then((response) => { const updatedWork = response.items.find( (item) => item.sourceSessionId === entry.profileId, ); if (!updatedWork) { return; } setBigFishGalleryEntries((current) => current.map((item) => mergeBigFishWorkSummary(item, updatedWork), ), ); setBigFishWorks((current) => current.map((item) => mergeBigFishWorkSummary(item, updatedWork), ), ); syncUpdatedPublicWorkDetail( mapBigFishWorkToPublicWorkDetail(updatedWork), ); setBigFishRuntimeWork((current) => current ? mergeBigFishWorkSummary(current, updatedWork) : current, ); }) .catch((error) => { setPublicWorkDetailError( resolveBigFishErrorMessage(error, '点赞大鱼吃小鱼作品失败。'), ); }) .finally(() => { setIsPublicWorkDetailBusy(false); }); return; } if (isPuzzleGalleryEntry(entry)) { void likePuzzleGalleryWork(entry.profileId) .then((response) => { const updatedWork = response.item; setPuzzleGalleryEntries((current) => current.map((item) => mergePuzzleWorkSummary(item, updatedWork), ), ); setPuzzleWorks((current) => current.map((item) => mergePuzzleWorkSummary(item, updatedWork), ), ); setSelectedPuzzleDetail((current) => current ? mergePuzzleWorkSummary(current, updatedWork) : current, ); syncUpdatedPublicWorkDetail( mapPuzzleWorkToPublicWorkDetail(updatedWork), ); }) .catch((error) => { setPublicWorkDetailError( resolvePuzzleErrorMessage(error, '点赞拼图作品失败。'), ); }) .finally(() => { setIsPublicWorkDetailBusy(false); }); return; } void likeRpgEntryWorldGallery(entry.ownerUserId, entry.profileId) .then((updatedEntry) => { setSelectedDetailEntry((current) => current?.profileId === updatedEntry.profileId ? updatedEntry : current, ); platformBootstrap.setPublishedGalleryEntries((current) => current.map((item) => item.profileId === updatedEntry.profileId ? updatedEntry : item, ), ); syncUpdatedPublicWorkDetail( mapRpgGalleryCardToPublicWorkDetail(updatedEntry), ); }) .catch((error) => { setPublicWorkDetailError( resolveRpgCreationErrorMessage(error, '点赞 RPG 作品失败。'), ); }) .finally(() => { setIsPublicWorkDetailBusy(false); }); }); }, [ isPublicWorkDetailBusy, platformBootstrap, resolveBigFishErrorMessage, resolvePuzzleErrorMessage, runProtectedAction, syncUpdatedPublicWorkDetail, ], ); useEffect(() => { const detailEntry = selectionStage === 'work-detail' ? selectedPublicWorkDetail : selectionStage === 'detail' && selectedDetailEntry && selectedDetailEntry.visibility !== 'draft' ? mapRpgGalleryCardToPublicWorkDetail(selectedDetailEntry) : null; if (!detailEntry) { clearSelectedPublicWorkAuthor(); return; } loadSelectedPublicWorkAuthor(detailEntry); }, [ clearSelectedPublicWorkAuthor, loadSelectedPublicWorkAuthor, selectedDetailEntry, selectedPublicWorkDetail, selectionStage, ]); const openRpgPublicWorkDetail = useCallback( async (entry: CustomWorldGalleryCard) => { setIsPublicWorkDetailBusy(true); setPublicWorkDetailError(null); clearSelectedPublicWorkAuthor(); setSelectedPublicWorkDetail(entry); setSelectionStage('work-detail'); try { const detailEntry = await detailNavigation.loadGalleryDetailEntry(entry); setSelectedDetailEntry(detailEntry); const detailCard = mapRpgGalleryCardToPublicWorkDetail(detailEntry); setSelectedPublicWorkDetail(detailCard); if (detailEntry.publicWorkCode?.trim()) { pushAppHistoryPath( buildPublicWorkStagePath('work-detail', detailEntry.publicWorkCode), ); } } catch (error) { setSelectedPublicWorkDetail(entry); setPublicWorkDetailError( resolveRpgCreationErrorMessage(error, '读取作品详情失败。'), ); } finally { setIsPublicWorkDetailBusy(false); } }, [ clearSelectedPublicWorkAuthor, detailNavigation, setSelectedDetailEntry, setSelectionStage, ], ); const openPuzzlePublicWorkDetail = useCallback( async ( profileId: string, returnTarget: PuzzleDetailReturnTarget = { tab: platformBootstrap.platformTab, }, ) => { setIsPuzzleBusy(true); setIsPublicWorkDetailBusy(true); setPuzzleError(null); setPublicWorkDetailError(null); setSelectionStage('work-detail'); try { const { item } = await getPuzzleGalleryDetail(profileId); setSelectedPuzzleDetail(item); setPuzzleDetailReturnTarget(returnTarget); openPublicWorkDetail(mapPuzzleWorkToPublicWorkDetail(item)); } catch (error) { setPublicWorkDetailError( resolvePuzzleErrorMessage(error, '读取拼图详情失败。'), ); } finally { setIsPuzzleBusy(false); setIsPublicWorkDetailBusy(false); } }, [ openPublicWorkDetail, platformBootstrap.platformTab, resolvePuzzleErrorMessage, setPuzzleError, ], ); const openMatch3DPublicWorkDetail = useCallback( async (profileId: string) => { setIsPublicWorkDetailBusy(true); setMatch3DError(null); setPublicWorkDetailError(null); setSelectionStage('work-detail'); try { const entries = match3dGalleryEntries.length > 0 ? match3dGalleryEntries : await refreshMatch3DGallery(); const matchedEntry = entries.find( (entry) => entry.profileId === profileId, ); if (!matchedEntry) { throw new Error('未找到抓大鹅作品。'); } openPublicWorkDetail(mapMatch3DWorkToPublicWorkDetail(matchedEntry)); } catch (error) { setPublicWorkDetailError( resolveMatch3DErrorMessage(error, '读取抓大鹅详情失败。'), ); } finally { setIsPublicWorkDetailBusy(false); } }, [ match3dGalleryEntries, openPublicWorkDetail, refreshMatch3DGallery, resolveMatch3DErrorMessage, setMatch3DError, setSelectionStage, ], ); const openPuzzleDetail = useCallback( async ( profileId: string, returnTarget: PuzzleDetailReturnTarget = { tab: platformBootstrap.platformTab, }, ) => { setIsPuzzleBusy(true); setPuzzleError(null); try { const { item } = await getPuzzleGalleryDetail(profileId); setSelectedPuzzleDetail(item); setPuzzleDetailReturnTarget(returnTarget); setSelectionStage('puzzle-gallery-detail'); pushAppHistoryPath( buildPublicWorkStagePath( 'puzzle-gallery-detail', buildPuzzlePublicWorkCode(item.profileId), ), ); } catch (error) { setPuzzleError(resolvePuzzleErrorMessage(error, '读取拼图详情失败。')); } finally { setIsPuzzleBusy(false); } }, [ platformBootstrap.platformTab, resolvePuzzleErrorMessage, setSelectionStage, ], ); const openPuzzleDraft = useCallback( async (item: PuzzleWorkSummary) => { setPuzzleOperation(null); setPuzzleRun(null); setSelectedPuzzleDetail(null); if (!item.sourceSessionId?.trim()) { if (item.publicationStatus === 'published') { await openPuzzleDetail(item.profileId, { tab: 'create' }); return; } setPuzzleError('这份拼图草稿缺少会话信息,请重新开始创作。'); return; } const restoredSession = await puzzleFlow.restoreDraft( item.sourceSessionId, ); if (!restoredSession) { await refreshPuzzleShelf().catch(() => undefined); return; } if (isPuzzleFormOnlyDraft(restoredSession)) { setPuzzleFormDraftPayload( buildPuzzleFormPayloadFromSession(restoredSession), ); setSelectionStage('puzzle-agent-workspace'); } else { setPuzzleFormDraftPayload(null); } }, [ openPuzzleDetail, puzzleFlow, refreshPuzzleShelf, setPuzzleError, setSelectionStage, ], ); const openMatch3DDraft = useCallback( async ( item: Match3DWorkSummary, options: { forceDraft?: boolean } = {}, ) => { setMatch3DRun(null); setMatch3DError(null); setMatch3DProfile(null); if (item.publicationStatus === 'published' && !options.forceDraft) { openPublicWorkDetail(mapMatch3DWorkToPublicWorkDetail(item)); return; } if (!item.sourceSessionId?.trim()) { setMatch3DError('这份抓大鹅草稿缺少会话信息,请重新开始创作。'); return; } const restoredSession = await match3dFlow.restoreDraft( item.sourceSessionId, ); if (!restoredSession) { await refreshMatch3DShelf().catch(() => undefined); return; } try { const { item: profile } = await getMatch3DWorkDetail(item.profileId); setMatch3DProfile(profile); } catch (error) { setMatch3DProfile(buildMatch3DProfileFromSession(restoredSession)); setMatch3DError( resolveMatch3DErrorMessage(error, '读取抓大鹅作品详情失败。'), ); } }, [ match3dFlow, openPublicWorkDetail, refreshMatch3DShelf, resolveMatch3DErrorMessage, setMatch3DError, ], ); const openBigFishDraft = useCallback( async (item: BigFishWorkSummary) => { setBigFishRun(null); const restoredSession = await bigFishFlow.restoreDraft( item.sourceSessionId, ); if (!restoredSession) { await refreshBigFishShelf().catch(() => undefined); } }, [bigFishFlow, refreshBigFishShelf], ); const startBigFishRunFromWork = useCallback( ( item: BigFishWorkSummary, returnStage: BigFishRuntimeReturnStage = 'work-detail', ) => { const sessionId = item.sourceSessionId?.trim(); if (!sessionId) { setBigFishError('当前作品缺少会话信息,暂时无法进入玩法。'); return; } const publicWorkCode = buildBigFishPublicWorkCode(item.sourceSessionId); setBigFishError(null); bigFishFlow.setSession(null); setBigFishRuntimeWork(item); setBigFishRuntimeShare({ title: item.title, publicWorkCode, }); setBigFishRuntimeStartedAt(Date.now()); setBigFishRuntimeReturnStage(returnStage); setBigFishRun(startLocalBigFishRuntimeRun({ work: item })); setSelectionStage('big-fish-runtime'); pushAppHistoryPath( buildPublicWorkStagePath('big-fish-runtime', publicWorkCode), ); void recordBigFishPlay(sessionId, { elapsedMs: 0 }).catch((error) => { setBigFishError( resolveBigFishErrorMessage(error, '记录大鱼吃小鱼游玩失败。'), ); }); }, [bigFishFlow, resolveBigFishErrorMessage, setSelectionStage], ); const startSelectedPublicWork = useCallback(() => { if (!selectedPublicWorkDetail || isPublicWorkDetailBusy) { return; } runProtectedAction(() => { if (isBigFishGalleryEntry(selectedPublicWorkDetail)) { const work = mapPublicWorkDetailToBigFishWork(selectedPublicWorkDetail); if (!work) { setPublicWorkDetailError('当前作品缺少会话信息,暂时无法进入玩法。'); return; } startBigFishRunFromWork(work); return; } if (isPuzzleGalleryEntry(selectedPublicWorkDetail)) { const work = mapPublicWorkDetailToPuzzleWork(selectedPublicWorkDetail); if (!work) { setPublicWorkDetailError( '当前拼图作品信息不完整,暂时无法进入玩法。', ); return; } setPublicWorkDetailError(null); void startPuzzleRunFromProfile( work.profileId, 'work-detail', work, true, ); return; } if (isMatch3DGalleryEntry(selectedPublicWorkDetail)) { const work = mapPublicWorkDetailToMatch3DWork(selectedPublicWorkDetail); if (!work) { setPublicWorkDetailError( '当前抓大鹅作品信息不完整,暂时无法进入玩法。', ); return; } setPublicWorkDetailError(null); void startMatch3DRunFromProfile(work, 'work-detail', true); return; } const launchEntry = selectedDetailEntry?.profileId === selectedPublicWorkDetail.profileId ? selectedDetailEntry : null; if (!launchEntry) { setPublicWorkDetailError('作品详情尚未读取完成。'); return; } setIsPublicWorkDetailBusy(true); void recordRpgEntryWorldGalleryPlay( launchEntry.ownerUserId, launchEntry.profileId, ) .then((updatedEntry) => { setSelectedDetailEntry(updatedEntry); setSelectedPublicWorkDetail( mapRpgGalleryCardToPublicWorkDetail(updatedEntry), ); handleCustomWorldSelect(updatedEntry.profile); }) .catch((error) => { setPublicWorkDetailError( resolveRpgCreationErrorMessage(error, '记录作品游玩失败。'), ); }) .finally(() => { setIsPublicWorkDetailBusy(false); }); }); }, [ handleCustomWorldSelect, isPublicWorkDetailBusy, runProtectedAction, selectedDetailEntry, selectedPublicWorkDetail, startBigFishRunFromWork, startMatch3DRunFromProfile, startPuzzleRunFromProfile, ]); const remixPublicWork = useCallback( (entry: PlatformPublicGalleryCard) => { if (isPublicWorkDetailBusy) { return; } runProtectedAction(() => { setIsPublicWorkDetailBusy(true); setPublicWorkDetailError(null); if (isBigFishGalleryEntry(entry)) { void remixBigFishGalleryWork(entry.profileId) .then((response) => { bigFishFlow.setSession(response.session); enterCreateTab(); setSelectionStage('big-fish-result'); }) .catch((error) => { setPublicWorkDetailError( resolveBigFishErrorMessage(error, 'Remix 大鱼吃小鱼作品失败。'), ); }) .finally(() => { setIsPublicWorkDetailBusy(false); }); return; } if (isPuzzleGalleryEntry(entry)) { void remixPuzzleGalleryWork(entry.profileId) .then((response) => { puzzleFlow.setSession(response.session); setPuzzleOperation(null); enterCreateTab(); setSelectionStage('puzzle-result'); }) .catch((error) => { setPublicWorkDetailError( resolvePuzzleErrorMessage(error, 'Remix 拼图作品失败。'), ); }) .finally(() => { setIsPublicWorkDetailBusy(false); }); return; } if (isMatch3DGalleryEntry(entry)) { setPublicWorkDetailError('抓大鹅作品改造将在后续版本开放。'); setIsPublicWorkDetailBusy(false); return; } void remixRpgEntryWorldGallery(entry.ownerUserId, entry.profileId) .then((response) => { const nextEntry = response.entry; setSelectedDetailEntry(nextEntry); platformBootstrap.setSavedCustomWorldEntries([ nextEntry, ...platformBootstrap.savedCustomWorldEntries.filter( (entry) => entry.profileId !== nextEntry.profileId, ), ]); void detailNavigation.openSavedCustomWorldEditor(nextEntry); }) .catch((error) => { setPublicWorkDetailError( resolveRpgCreationErrorMessage(error, 'Remix RPG 作品失败。'), ); }) .finally(() => { setIsPublicWorkDetailBusy(false); }); }); }, [ bigFishFlow, detailNavigation, enterCreateTab, isPublicWorkDetailBusy, platformBootstrap, puzzleFlow, resolveBigFishErrorMessage, resolvePuzzleErrorMessage, runProtectedAction, setSelectionStage, ], ); const editOwnedPublicWork = useCallback( (entry: PlatformPublicGalleryCard) => { if (isPublicWorkDetailBusy) { return; } runProtectedAction(() => { setPublicWorkDetailError(null); // 中文注释:自有公开作品必须恢复原草稿,不能复用 remix 复制链路。 if (isBigFishGalleryEntry(entry)) { const work = mapPublicWorkDetailToBigFishWork(entry); if (!work?.sourceSessionId?.trim()) { setPublicWorkDetailError( '这份大鱼吃小鱼作品缺少原草稿会话,暂时无法编辑。', ); return; } void openBigFishDraft(work); return; } if (isPuzzleGalleryEntry(entry)) { const work = selectedPuzzleDetail?.profileId === entry.profileId ? selectedPuzzleDetail : mapPublicWorkDetailToPuzzleWork(entry); if (!work?.sourceSessionId?.trim()) { setPublicWorkDetailError( '这份拼图作品缺少原草稿会话,暂时无法编辑。', ); return; } void openPuzzleDraft(work); return; } if (isMatch3DGalleryEntry(entry)) { const work = mapPublicWorkDetailToMatch3DWork(entry); if (!work?.sourceSessionId?.trim()) { setPublicWorkDetailError( '这份抓大鹅作品缺少原草稿会话,暂时无法编辑。', ); return; } void openMatch3DDraft(work, { forceDraft: true }); return; } const editEntry = selectedDetailEntry?.profileId === entry.profileId ? selectedDetailEntry : null; if (!editEntry) { setPublicWorkDetailError('作品详情尚未读取完成。'); return; } void detailNavigation.openSavedCustomWorldEditor(editEntry); }); }, [ detailNavigation, isPublicWorkDetailBusy, openBigFishDraft, openMatch3DDraft, openPuzzleDraft, runProtectedAction, selectedDetailEntry, selectedPuzzleDetail, ], ); const remixSelectedPublicWork = useCallback(() => { if (!selectedPublicWorkDetail) { return; } if (isSelectedPublicWorkOwned) { editOwnedPublicWork(selectedPublicWorkDetail); return; } remixPublicWork(selectedPublicWorkDetail); }, [ editOwnedPublicWork, isSelectedPublicWorkOwned, remixPublicWork, selectedPublicWorkDetail, ]); const handlePublicCodeSearch = useCallback( async (keyword: string) => { const normalizedKeyword = keyword.trim(); if (!normalizedKeyword) { return; } setIsSearchingPublicCode(true); setPublicSearchError(null); setSearchedPublicUser(null); const upperKeyword = normalizedKeyword.toUpperCase(); const shouldSearchUserIdFirst = /^user[_-][a-z0-9_-]+$/iu.test( normalizedKeyword, ); const shouldSearchBigFishFirst = upperKeyword.startsWith('BF'); const shouldSearchMatch3DFirst = upperKeyword.startsWith('M3'); const shouldSearchPuzzleFirst = upperKeyword.startsWith('PZ'); const shouldSearchWorkFirst = !shouldSearchUserIdFirst && !shouldSearchBigFishFirst && !shouldSearchMatch3DFirst && !shouldSearchPuzzleFirst && (upperKeyword.startsWith('CW') || /^\d{1,8}$/u.test(normalizedKeyword)); const shouldSearchUserFirst = shouldSearchUserIdFirst || upperKeyword.startsWith('SY') || (!shouldSearchWorkFirst && !shouldSearchBigFishFirst && !shouldSearchMatch3DFirst && !shouldSearchPuzzleFirst); const tryOpenGalleryEntry = async () => { const entry = await getRpgEntryWorldGalleryDetailByCode(normalizedKeyword); const card = { ownerUserId: entry.ownerUserId, profileId: entry.profileId, publicWorkCode: entry.publicWorkCode, authorPublicUserCode: entry.authorPublicUserCode, visibility: 'published', publishedAt: entry.publishedAt, updatedAt: entry.updatedAt, authorDisplayName: entry.authorDisplayName, worldName: entry.worldName, subtitle: entry.subtitle, summaryText: entry.summaryText, coverImageSrc: entry.coverImageSrc, themeMode: entry.themeMode, playableNpcCount: entry.playableNpcCount, landmarkCount: entry.landmarkCount, playCount: entry.playCount ?? 0, remixCount: entry.remixCount ?? 0, likeCount: entry.likeCount ?? 0, } satisfies CustomWorldGalleryCard; setSelectedDetailEntry(entry); openPublicWorkDetail(card); }; const tryOpenPuzzleGalleryEntry = async () => { const entries = puzzleGalleryEntries.length > 0 ? puzzleGalleryEntries : await refreshPuzzleGallery(); const matchedEntry = entries.find((entry) => isSamePuzzlePublicWorkCode(normalizedKeyword, entry.profileId), ); if (!matchedEntry) { throw new Error('未找到拼图作品。'); } await openPuzzlePublicWorkDetail(matchedEntry.profileId, { tab: platformBootstrap.platformTab, }); }; const tryOpenBigFishGalleryEntry = async () => { const entries = bigFishGalleryEntries.length > 0 ? bigFishGalleryEntries : await refreshBigFishGallery(); const matchedEntry = entries.find((entry) => isSameBigFishPublicWorkCode(normalizedKeyword, entry.sourceSessionId), ); if (!matchedEntry) { throw new Error('未找到大鱼吃小鱼作品。'); } openPublicWorkDetail(mapBigFishWorkToPublicWorkDetail(matchedEntry)); }; const tryOpenMatch3DGalleryEntry = async () => { const entries = match3dGalleryEntries.length > 0 ? match3dGalleryEntries : await refreshMatch3DGallery(); const matchedEntry = entries.find((entry) => isSameMatch3DPublicWorkCode(normalizedKeyword, entry.profileId), ); if (!matchedEntry) { throw new Error('未找到抓大鹅作品。'); } openPublicWorkDetail(mapMatch3DWorkToPublicWorkDetail(matchedEntry)); }; try { if (shouldSearchUserIdFirst) { const user = await getPublicAuthUserById(normalizedKeyword); setSearchedPublicUser(user); return; } if (shouldSearchPuzzleFirst) { await tryOpenPuzzleGalleryEntry(); return; } if (shouldSearchBigFishFirst) { await tryOpenBigFishGalleryEntry(); return; } if (shouldSearchMatch3DFirst) { await tryOpenMatch3DGalleryEntry(); return; } if (shouldSearchWorkFirst) { try { await tryOpenGalleryEntry(); return; } catch { // 作品号优先时允许继续回退到用户号搜索。 } } if (shouldSearchUserFirst) { try { const user = await getPublicAuthUserByCode(normalizedKeyword); setSearchedPublicUser(user); return; } catch { // 用户号优先时允许继续回退到作品号搜索。 } } if (!shouldSearchWorkFirst) { await tryOpenGalleryEntry(); return; } const user = await getPublicAuthUserByCode(normalizedKeyword); setSearchedPublicUser(user); } catch (error) { setPublicSearchError( resolveRpgCreationErrorMessage(error, '未找到对应的百梦号或作品号。'), ); } finally { setIsSearchingPublicCode(false); } }, [ bigFishGalleryEntries, match3dGalleryEntries, refreshMatch3DGallery, openPuzzlePublicWorkDetail, openPublicWorkDetail, platformBootstrap.platformTab, puzzleGalleryEntries, refreshBigFishGallery, refreshPuzzleGallery, ], ); const openProfilePlayedWorks = useCallback(() => { setIsProfilePlayStatsOpen(true); setIsProfilePlayStatsLoading(true); setProfilePlayStatsError(null); void getRpgProfilePlayStats() .then(setProfilePlayStats) .catch((error) => { setProfilePlayStats(null); setProfilePlayStatsError( resolveRpgCreationErrorMessage(error, '读取玩过作品失败。'), ); }) .finally(() => { setIsProfilePlayStatsLoading(false); }); }, []); const openPlayedWork = useCallback( (work: ProfilePlayedWorkSummary) => { const worldType = (work.worldType ?? '').toLowerCase(); setIsProfilePlayStatsOpen(false); if (worldType === 'puzzle' || work.worldKey.startsWith('puzzle:')) { const profileId = work.profileId ?? work.worldKey.replace(/^puzzle:/u, ''); if (profileId) { void openPuzzlePublicWorkDetail(profileId, { tab: 'profile' }); } return; } if ( worldType === 'match3d' || worldType === 'match_3d' || work.worldKey.startsWith('match3d:') ) { const profileId = work.profileId ?? work.worldKey.replace(/^match3d:/u, ''); if (profileId) { void openMatch3DPublicWorkDetail(profileId); } return; } if ( worldType === 'big_fish' || worldType === 'big-fish' || work.worldKey.startsWith('big-fish:') ) { const sessionId = work.profileId ?? work.worldKey.replace(/^big-fish:/u, ''); if (!sessionId) { return; } void refreshBigFishGallery() .then((entries) => { const matchedEntry = entries.find( (entry) => entry.sourceSessionId === sessionId, ); if (matchedEntry) { openPublicWorkDetail( mapBigFishWorkToPublicWorkDetail(matchedEntry), ); return; } openPublicWorkDetail( mapBigFishWorkToPublicWorkDetail({ workId: `big-fish:${sessionId}`, sourceSessionId: sessionId, ownerUserId: work.ownerUserId ?? '', authorDisplayName: work.worldSubtitle || '玩家', title: work.worldTitle, subtitle: work.worldSubtitle, summary: work.worldSubtitle, coverImageSrc: null, status: 'published', updatedAt: work.lastPlayedAt, publishReady: true, levelCount: 0, levelMainImageReadyCount: 0, levelMotionReadyCount: 0, backgroundReady: false, }), ); }) .catch((error) => { setBigFishError( resolveBigFishErrorMessage(error, '进入大鱼吃小鱼作品失败。'), ); }); return; } const profileId = work.profileId ?? work.worldKey; const ownerUserId = work.ownerUserId; if (!ownerUserId || !profileId) { return; } void openRpgPublicWorkDetail({ ownerUserId, profileId, publicWorkCode: null, authorPublicUserCode: null, visibility: 'published', publishedAt: work.firstPlayedAt, updatedAt: work.lastPlayedAt, authorDisplayName: work.worldSubtitle, worldName: work.worldTitle, subtitle: work.worldSubtitle, summaryText: '', coverImageSrc: null, themeMode: 'martial', playableNpcCount: 0, landmarkCount: 0, playCount: 0, remixCount: 0, likeCount: 0, }); }, [ openMatch3DPublicWorkDetail, openPuzzlePublicWorkDetail, openPublicWorkDetail, openRpgPublicWorkDetail, refreshBigFishGallery, resolveBigFishErrorMessage, ], ); useEffect(() => { const publicWorkCode = initialPublicWorkCode?.trim(); if ( !publicWorkCode || handledInitialPublicWorkCodeRef.current === publicWorkCode ) { return; } handledInitialPublicWorkCodeRef.current = publicWorkCode; void handlePublicCodeSearch(publicWorkCode); }, [handlePublicCodeSearch, initialPublicWorkCode]); useEffect(() => { if (selectionStage === 'platform') { if (isBigFishCreationVisible) { void refreshBigFishGallery(); } void refreshMatch3DGallery(); void refreshPuzzleGallery(); } }, [ isBigFishCreationVisible, refreshBigFishGallery, refreshMatch3DGallery, refreshPuzzleGallery, selectionStage, ]); useEffect(() => { if ( (platformBootstrap.platformTab === 'create' || selectionStage === 'platform') && platformBootstrap.canReadProtectedData ) { void refreshPuzzleShelf(); void refreshMatch3DShelf(); } }, [ platformBootstrap.canReadProtectedData, platformBootstrap.platformTab, refreshMatch3DShelf, refreshPuzzleShelf, selectionStage, ]); useEffect(() => { if ( isBigFishCreationVisible && (platformBootstrap.platformTab === 'create' || selectionStage === 'platform') && platformBootstrap.canReadProtectedData ) { void refreshBigFishShelf(); } }, [ isBigFishCreationVisible, platformBootstrap.canReadProtectedData, platformBootstrap.platformTab, refreshBigFishShelf, selectionStage, ]); const creationHubContent = ( }> { platformBootstrap.setPlatformError(null); setBigFishError(null); setMatch3DError(null); setPuzzleError(null); void platformBootstrap.refreshCustomWorldWorks().catch((error) => { platformBootstrap.setPlatformError( resolveRpgCreationErrorMessage(error, '读取创作作品列表失败。'), ); }); if (isBigFishCreationVisible) { void refreshBigFishShelf(); } void refreshMatch3DShelf(); void refreshPuzzleShelf(); }} createError={ sessionController.creationTypeError ?? bigFishError ?? match3dError ?? puzzleError } createBusy={ sessionController.isCreatingAgentSession || isBigFishBusy || isMatch3DBusy || isPuzzleBusy } onCreateType={handleCreationHubCreateType} onOpenDraft={(item) => { runProtectedAction(() => { void detailNavigation.handleOpenCreationWork(item); }); }} onEnterPublished={(profileId) => { runProtectedAction(() => { const matchedWork = creationHubItems.find( (entry) => entry.profileId === profileId, ); if (!matchedWork) { return; } void detailNavigation.handleOpenCreationWork(matchedWork); }); }} onDeletePublished={(item) => { handleDeletePublishedWork(item); }} deletingWorkId={deletingCreationWorkId} rpgLibraryEntries={platformBootstrap.savedCustomWorldEntries} bigFishItems={isBigFishCreationVisible ? bigFishWorks : []} onOpenBigFishDetail={ isBigFishCreationVisible ? (item) => { runProtectedAction(() => { void openBigFishDraft(item); }); } : undefined } onDeleteBigFish={ isBigFishCreationVisible ? (item) => { handleDeleteBigFishWork(item); } : null } match3dItems={match3dWorks} onOpenMatch3DDetail={(item) => { runProtectedAction(() => { void openMatch3DDraft(item); }); }} onDeleteMatch3D={(item) => { handleDeleteMatch3DWork(item); }} puzzleItems={puzzleWorks} onOpenPuzzleDetail={(item) => { runProtectedAction(() => { void openPuzzleDraft(item); }); }} onDeletePuzzle={(item) => { handleDeletePuzzleWork(item); }} onClaimPuzzlePointIncentive={(item) => { handleClaimPuzzlePointIncentive(item); }} claimingPuzzleProfileId={claimingPuzzlePointIncentiveProfileId} /> ); return ( <> {selectionStage === 'platform' && ( { if ( (entry.worldType ?? '').toLowerCase() === 'puzzle' || entry.worldKey.startsWith('puzzle:') ) { void resumePuzzleSaveArchive(entry); return; } void platformBootstrap.handleResumeSaveEntry(entry); }} onOpenCreateWorld={openCreationTypePicker} onOpenCreateTypePicker={openCreationTypePicker} onOpenGalleryDetail={(entry) => { if (isBigFishGalleryEntry(entry)) { openPublicWorkDetail(entry); return; } if (isPuzzleGalleryEntry(entry)) { void openPuzzlePublicWorkDetail(entry.profileId, { tab: platformBootstrap.platformTab, }); return; } if (isMatch3DGalleryEntry(entry)) { openPublicWorkDetail(entry); return; } void openRpgPublicWorkDetail(entry); }} onOpenLibraryDetail={(entry) => { runProtectedAction(() => { void detailNavigation.openLibraryDetail(entry); }); }} onDeleteLibraryEntry={(entry) => { handleDeleteLibraryEntry(entry); }} deletingLibraryEntryId={deletingCreationWorkId} onSearchPublicCode={(keyword) => { void handlePublicCodeSearch(keyword); }} isSearchingPublicCode={isSearchingPublicCode} profilePlayStats={profilePlayStats} isProfilePlayStatsOpen={isProfilePlayStatsOpen} isProfilePlayStatsLoading={isProfilePlayStatsLoading} profilePlayStatsError={profilePlayStatsError} onCloseProfilePlayStats={() => { setIsProfilePlayStatsOpen(false); }} onOpenPlayedWork={openPlayedWork} onOpenProfileDashboardCard={(cardKey) => { if (cardKey === 'playedWorks') { openProfilePlayedWorks(); return; } if (platformBootstrap.dashboardError) { void platformBootstrap.refreshProfileDashboard(); } }} onRechargeSuccess={platformBootstrap.refreshProfileDashboard} /> )} {selectionStage === 'work-detail' && selectedPublicWorkDetail && ( { setPublicWorkDetailError(null); clearSelectedPublicWorkAuthor(); setSelectionStage('platform'); }} onLike={() => { likePublicWork(selectedPublicWorkDetail); }} onStart={startSelectedPublicWork} onRemix={remixSelectedPublicWork} /> )} {selectionStage === 'detail' && ( {detailNavigation.isDetailLoading || !selectedDetailEntry ? (
{detailNavigation.detailError || '正在读取作品详情...'}
) : selectedDetailEntry.visibility !== 'draft' ? ( { detailNavigation.setDetailError(null); clearSelectedPublicWorkAuthor(); entryNavigation.backToPlatformHome(); }} onLike={() => { likePublicWork( mapRpgGalleryCardToPublicWorkDetail(selectedDetailEntry), ); }} onStart={handleStartSelectedWorld} onRemix={() => { const publicWorkEntry = mapRpgGalleryCardToPublicWorkDetail(selectedDetailEntry); if (detailNavigation.isSelectedWorldOwned) { editOwnedPublicWork(publicWorkEntry); return; } remixPublicWork(publicWorkEntry); }} /> ) : ( { detailNavigation.setDetailError(null); entryNavigation.backToPlatformHome(); }} onStartGame={handleStartSelectedWorld} onContinueEdit={ detailNavigation.isSelectedWorldOwned ? () => { runProtectedAction(() => { void detailNavigation.openSavedCustomWorldEditor( selectedDetailEntry, ); }); } : null } onPublish={ selectedDetailEntry.visibility === 'draft' && detailNavigation.isSelectedWorldOwned ? () => { runProtectedAction(() => { void detailNavigation.handlePublishSelectedWorld(); }); } : null } onUnpublish={ selectedDetailEntry.visibility !== 'draft' && detailNavigation.isSelectedWorldOwned ? () => { runProtectedAction(() => { void detailNavigation.handleUnpublishSelectedWorld(); }); } : null } onDelete={ detailNavigation.isSelectedWorldOwned ? () => { runProtectedAction(() => { void detailNavigation.handleDeleteSelectedWorld(); }); } : null } /> )}
)} {selectionStage === 'agent-workspace' && ( } > {sessionController.agentSession ? ( { void sessionController.submitAgentMessage(payload); }} onExecuteAction={(payload) => { void sessionController.executeAgentAction(payload); }} /> ) : (
{sessionController.isLoadingAgentSession ? '正在准备 Agent 共创工作区...' : sessionController.agentWorkspaceRestoreError || '正在恢复创作工作区...'}
)}
)} {selectionStage === 'big-fish-agent-workspace' && ( } > { void submitBigFishMessage(payload); }} onExecuteAction={(payload) => { void executeBigFishAction(payload); }} /> )} {selectionStage === 'big-fish-generating' && ( } > { 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="等待返回工作区" /> )} {selectionStage === 'big-fish-result' && bigFishSession?.draft && ( } > { setSelectionStage('big-fish-agent-workspace'); }} onDismissError={() => { setBigFishError(null); }} onExecuteAction={(payload) => { void executeBigFishAction(payload); }} onStartTestRun={() => { void startBigFishRun(); }} /> )} {selectionStage === 'big-fish-runtime' && ( } > { reportBigFishObservedPlayTime(); setSelectionStage(bigFishRuntimeReturnStage); }} onRestart={() => { reportBigFishObservedPlayTime(); void restartBigFishRun(); }} onSubmitInput={submitBigFishInput} /> )} {selectionStage === 'match3d-agent-workspace' && ( } > { void submitMatch3DMessage(payload); }} onExecuteAction={(payload) => { void executeMatch3DAction(payload); }} /> )} {selectionStage === 'match3d-result' && match3dSession?.draft && ( } > { setSelectionStage('match3d-agent-workspace'); }} onSaved={(profile) => { setMatch3DProfile(profile); }} onPublished={(profile) => { setMatch3DProfile(profile); void Promise.allSettled([ refreshMatch3DShelf(), refreshMatch3DGallery(), ]); openPublicWorkDetail( mapMatch3DWorkToPublicWorkDetail(profile), ); openPublishShareModal({ title: profile.gameName, publicWorkCode: buildMatch3DPublicWorkCode( profile.profileId, ), stage: 'work-detail', }); }} onStartTestRun={(profile) => { setMatch3DProfile(profile); void startMatch3DRunFromProfile(profile, 'match3d-result'); }} /> )} {selectionStage === 'match3d-runtime' && ( } > { if (match3dRun?.runId && match3dRun.status === 'running') { void stopMatch3DRun(match3dRun.runId).catch( () => undefined, ); } setSelectionStage(match3dRuntimeReturnStage); }} onRestart={() => { if (!match3dRun?.runId || isMatch3DBusy) { return; } match3dFlow.setIsBusy(true); setMatch3DError(null); void restartMatch3DRun(match3dRun.runId) .then(({ run }) => { setMatch3DRun(run); }) .catch((error) => { setMatch3DError( resolveMatch3DErrorMessage( error, '重新开始抓大鹅玩法失败。', ), ); }) .finally(() => { match3dFlow.setIsBusy(false); }); }} onOptimisticRunChange={setMatch3DRun} onClickItem={(payload) => { const runId = payload.runId ?? match3dRun?.runId; if (!runId) { return Promise.reject( new Error('抓大鹅运行态缺少 runId。'), ); } return clickMatch3DItem(runId, payload); }} onTimeExpired={() => { if (!match3dRun?.runId) { return; } void finishMatch3DTimeUp(match3dRun.runId) .then(({ run }) => { setMatch3DRun(run); }) .catch((error) => { setMatch3DError( resolveMatch3DErrorMessage( error, '同步抓大鹅倒计时失败。', ), ); }); }} /> )} {selectionStage === 'puzzle-agent-workspace' && ( } > { void submitPuzzleMessage(payload); }} onExecuteAction={(payload) => { executePuzzleWorkspaceAction(payload); }} initialFormPayload={puzzleFormDraftPayload} onCreateFromForm={(payload) => { void createPuzzleDraftFromForm(payload); }} onAutoSaveForm={(payload) => { void savePuzzleFormDraft(payload); }} /> )} {selectionStage === 'puzzle-generating' && ( } > { setSelectionStage('puzzle-agent-workspace'); }} onRetry={retryPuzzleDraftGeneration} onInterrupt={undefined} backLabel="返回创作中心" settingActionLabel={null} retryLabel="重新生成草稿" settingTitle="当前拼图信息" settingDescription={null} progressTitle="拼图草稿生成进度" activeBadgeLabel="草稿生成中" pausedBadgeLabel="草稿生成已暂停" idleBadgeLabel="等待返回工作区" /> )} {selectionStage === 'puzzle-result' && puzzleSession?.draft && !isPuzzleFormOnlyDraft(puzzleSession) && ( } > { setSelectionStage('puzzle-agent-workspace'); }} onExecuteAction={(payload) => { void executePuzzleAction(payload); }} onStartTestRun={startPuzzleTestRunFromDraft} /> )} {selectionStage === 'puzzle-gallery-detail' && selectedPuzzleDetail && ( } > { platformBootstrap.setPlatformTab( puzzleDetailReturnTarget?.tab ?? 'home', ); setPuzzleDetailReturnTarget(null); setSelectionStage('platform'); }} onEdit={ selectedPuzzleDetail.ownerUserId === authUi?.user?.id && Boolean(selectedPuzzleDetail.sourceSessionId?.trim()) ? () => { runProtectedAction(() => { void openPuzzleDraft(selectedPuzzleDetail); }); } : null } onStartGame={() => { void startPuzzleRunFromProfile( selectedPuzzleDetail.profileId, 'puzzle-gallery-detail', selectedPuzzleDetail, ); }} /> )} {selectionStage === 'puzzle-runtime' && ( } > { setSelectionStage(puzzleRuntimeReturnStage); }} onRemodelWork={ selectedPuzzleDetail?.publicationStatus === 'published' ? remodelCurrentPuzzleRuntimeWork : undefined } onSwapPieces={(payload) => { void swapPuzzlePiecesInRun(payload); }} onDragPiece={(payload) => { void dragPuzzlePiece(payload); }} onAdvanceNextLevel={(target) => { void advancePuzzleLevel(target); }} onRestartLevel={() => { void restartPuzzleCurrentLevel(); }} onPauseChange={setPuzzleRuntimePaused} onUseProp={usePuzzleProp} onTimeExpired={syncPuzzleRuntimeTimeout} /> {isPuzzleNextLevelGenerating ? (
正在准备下一关
广场暂无可接续作品,正在生成新的候选图。
) : null}
)} {selectionStage === 'custom-world-generating' && ( } > { void sessionController.executeAgentAction({ action: 'draft_foundation', }); }} onInterrupt={undefined} backLabel="返回工作区" settingActionLabel={null} retryLabel="继续生成草稿" settingTitle="当前世界信息" settingDescription={null} progressTitle="世界草稿生成进度" activeBadgeLabel="草稿编译中" pausedBadgeLabel="草稿生成已暂停" idleBadgeLabel="等待返回工作区" /> )} {selectionStage === 'custom-world-result' && sessionController.generatedCustomWorldProfile && ( } > { sessionController.setGeneratedCustomWorldProfile(profile); }} onBack={ sessionController.isAgentDraftResultView ? () => { leaveAgentDraftResult(); } : leaveCustomWorldResult } onEditSetting={undefined} onRegenerate={undefined} onContinueExpand={undefined} onEnterWorld={() => { runProtectedAction(() => { void enterWorldCoordinator .enterWorldFromCurrentResult() .catch((error) => { sessionController.setCustomWorldError( resolveRpgCreationErrorMessage( error, '发布并进入世界失败。', ), ); }); }); }} onTestWorld={ sessionController.isAgentDraftResultView && sessionController.agentSession?.stage !== 'published' ? () => { runProtectedAction(() => { void enterWorldCoordinator .enterWorldForTestFromCurrentResult() .catch((error) => { sessionController.setCustomWorldError( resolveRpgCreationErrorMessage( error, '进入作品测试失败。', ), ); }); }); } : undefined } onPublishWorld={ sessionController.isAgentDraftResultView && sessionController.agentSession?.stage !== 'published' ? async () => { try { const publishedProfile = await enterWorldCoordinator.publishCurrentResult(); void openRpgPublishShareModal(publishedProfile); } catch (error) { sessionController.setCustomWorldError( resolveRpgCreationErrorMessage( error, '发布到广场失败。', ), ); throw error; } } : undefined } onGenerateEntity={ sessionController.isAgentDraftResultView ? async (kind) => { const action = kind === 'landmark' ? 'generate_landmarks' : 'generate_characters'; await autosaveCoordinator.executeAgentActionAndWait({ action, count: 1, ...(kind === 'playable' ? { roleType: 'playable' as const } : kind === 'story' ? { roleType: 'story' as const } : {}), }); const latestView = sessionController.activeAgentSessionId ? await sessionController.syncAgentCreationResultView( sessionController.activeAgentSessionId, ) : null; const latestProfile = rpgCreationPreviewAdapter.buildPreviewFromResultView( latestView, ); if (latestProfile) { sessionController.setGeneratedCustomWorldProfile( latestProfile, ); } return { profile: latestProfile }; } : undefined } onDeleteEntities={ sessionController.isAgentDraftResultView ? async (kind, ids) => { if (ids.length === 0) return; await autosaveCoordinator.executeAgentActionAndWait( kind === 'story' ? { action: 'delete_characters', roleIds: ids } : { action: 'delete_landmarks', sceneIds: ids }, ); const latestView = sessionController.activeAgentSessionId ? await sessionController.syncAgentCreationResultView( sessionController.activeAgentSessionId, ) : null; const latestProfile = rpgCreationPreviewAdapter.buildPreviewFromResultView( latestView, ); if (latestProfile) { sessionController.setGeneratedCustomWorldProfile( latestProfile, ); } } : undefined } readOnly={false} compactAgentResultMode={ sessionController.isAgentDraftResultView } backLabel={ sessionController.isAgentDraftResultView ? '返回创作' : undefined } editActionLabel="继续调整设定" enterWorldActionLabel={ sessionController.isAgentDraftResultView && sessionController.agentSession?.stage !== 'published' ? '发布并进入世界' : '进入世界' } publishReady={ sessionController.isAgentDraftResultView ? agentResultPublishGateView.publishReady : true } publishBlockers={ sessionController.isAgentDraftResultView ? agentResultPublishGateView.blockers : [] } qualityFindings={ sessionController.isAgentDraftResultView ? agentResultPreviewQualityFindings : [] } previewSourceLabel={ sessionController.isAgentDraftResultView ? agentResultPreviewSourceLabel : null } autoSaveState={autosaveCoordinator.customWorldAutoSaveState} /> )}
{ if ( sessionController.isCreatingAgentSession || isBigFishBusy || isMatch3DBusy || isPuzzleBusy ) { return; } setShowCreationTypeModal(false); }} onSelectRpg={() => { runProtectedAction(() => { void sessionController.openRpgAgentWorkspace(); }); }} onSelectBigFish={() => { runProtectedAction(() => { void openBigFishAgentWorkspace(); }); }} onSelectMatch3D={() => { runProtectedAction(() => { void openMatch3DAgentWorkspace(); }); }} onSelectPuzzle={() => { runProtectedAction(() => { void openPuzzleAgentWorkspace(); }); }} /> setPublishSharePayload(null)} /> } >
{pendingDeleteCreationWork?.detail}
{(searchedPublicUser || publicSearchError) && (
公开编号搜索
{publicSearchError ? '未找到结果' : '命中用户'}
{publicSearchError ? (
{publicSearchError}
) : searchedPublicUser ? (
{searchedPublicUser.displayName}
百梦号 {searchedPublicUser.publicUserCode}
) : null}
)}
); } export default PlatformEntryFlowShellImpl;