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 { 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, SubmitPuzzleLeaderboardRequest, } from '../../../packages/shared/src/contracts/puzzleRuntimeSession'; import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary'; import type { CustomWorldGalleryCard, CustomWorldLibraryEntry, ProfilePlayedWorkSummary, ProfilePlayStatsResponse, } 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 { 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 { buildBigFishGenerationAnchorEntries, buildMiniGameDraftGenerationProgress, buildPuzzleGenerationAnchorEntries, createMiniGameDraftGenerationState, type MiniGameDraftGenerationState, } from '../../services/miniGameDraftGenerationProgress'; import { getPlatformProfileDashboard } from '../../services/platform-entry/platformProfileClient'; import { buildBigFishPublicWorkCode, buildPuzzlePublicWorkCode, isSameBigFishPublicWorkCode, isSamePuzzlePublicWorkCode, } from '../../services/publicWorkCode'; import { createPuzzleAgentSession, executePuzzleAgentAction, getPuzzleAgentSession, streamPuzzleAgentMessage, } from '../../services/puzzle-agent'; import { getPuzzleGalleryDetail, listPuzzleGallery, remixPuzzleGalleryWork, } from '../../services/puzzle-gallery'; import { advanceLocalPuzzleNextLevel, getPuzzleRun, startPuzzleRun, submitPuzzleLeaderboard, updatePuzzleRunPause, usePuzzleRuntimeProp as consumePuzzleRuntimeProp, } from '../../services/puzzle-runtime'; import { applyLocalPuzzleFreezeTime, dragLocalPuzzlePiece, isLocalPuzzleRun, refreshLocalPuzzleTimer, setLocalPuzzlePaused, startLocalPuzzleRun, submitLocalPuzzleLeaderboard, swapLocalPuzzlePieces, } from '../../services/puzzle-runtime/puzzleLocalRuntime'; import { deletePuzzleWork, listPuzzleWorks } from '../../services/puzzle-works'; import { deleteRpgCreationAgentSession } from '../../services/rpg-creation'; import { rpgCreationPreviewAdapter } from '../../services/rpg-creation/rpgCreationPreviewAdapter'; import { deleteRpgEntryWorldProfile, getRpgEntryWorldGalleryDetailByCode, recordRpgEntryWorldGalleryPlay, remixRpgEntryWorldGallery, } from '../../services/rpg-entry/rpgEntryLibraryClient'; import { getRpgProfilePlayStats } from '../../services/rpg-entry/rpgProfileClient'; import type { CustomWorldProfile } from '../../types'; import { useAuthUi } from '../auth/AuthUiContext'; import { isBigFishGalleryEntry, isPuzzleGalleryEntry, mapBigFishWorkToPlatformGalleryCard, 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 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' : 'rpg'; return `${kind}:${entry.ownerUserId}:${entry.profileId}`; } 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 mapBigFishWorkToPublicWorkDetail( item: BigFishWorkSummary, ): PlatformPublicGalleryCard { return mapBigFishWorkToPlatformGalleryCard(item); } function mapPublicWorkDetailToPuzzleWork( entry: PlatformPublicGalleryCard, ): PuzzleWorkSummary | null { if (!isPuzzleGalleryEntry(entry)) { return null; } return { workId: entry.workId, profileId: entry.profileId, ownerUserId: entry.ownerUserId, 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, publishReady: true, }; } 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, 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, }; } 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 { return { action: 'compile_puzzle_draft', promptText: payload?.pictureDescription?.trim() || payload?.seedText?.trim(), referenceImageSrc: payload?.referenceImageSrc || null, candidateCount: 1, }; } 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 Match3DDraftReadyView = lazy(async () => { const module = await import('../match3d-creation/Match3DDraftReadyView'); return { default: module.Match3DDraftReadyView, }; }); 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; const leaderboardEntries = serviceLevel.leaderboardEntries.length > 0 ? serviceLevel.leaderboardEntries : serviceRun.leaderboardEntries; return { ...currentRun, leaderboardEntries, currentLevel: { ...currentRun.currentLevel, 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 [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 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 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[]), 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 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 puzzlePublicEntries = puzzleGalleryEntries.map( mapPuzzleWorkToPlatformGalleryCard, ); return mergePlatformPublicGalleryEntries( platformBootstrap.publishedGalleryEntries, [...bigFishPublicEntries, ...puzzlePublicEntries], ).slice(0, 6); }, [ isBigFishCreationVisible, bigFishGalleryEntries, platformBootstrap.publishedGalleryEntries, puzzleGalleryEntries, ]); const latestGalleryEntries = useMemo( () => mergePlatformPublicGalleryEntries( platformBootstrap.publishedGalleryEntries, [ ...(isBigFishCreationVisible ? bigFishGalleryEntries.map(mapBigFishWorkToPlatformGalleryCard) : []), ...puzzleGalleryEntries.map(mapPuzzleWorkToPlatformGalleryCard), ], ), [ isBigFishCreationVisible, bigFishGalleryEntries, platformBootstrap.publishedGalleryEntries, puzzleGalleryEntries, ], ); const creationHubItems = platformBootstrap.customWorldWorkEntries.length > 0 ? platformBootstrap.customWorldWorkEntries : buildCreationHubFallbackItems( platformBootstrap.savedCustomWorldEntries, ); const resultViewError = autosaveCoordinator.customWorldAutoSaveError ?? sessionController.customWorldError; 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 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(); } 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: ({ response, setSession }) => { setSession(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); 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), ), ); } }, beforeExecuteAction: ({ payload }) => { if (payload.action !== 'compile_puzzle_draft') { return; } setSelectionStage('puzzle-generating'); setPuzzleGenerationState(createMiniGameDraftGenerationState('puzzle')); }, onActionError: ({ payload, errorMessage }) => { if (payload.action !== 'compile_puzzle_draft') { return; } setPuzzleGenerationState((current) => current ? { ...current, phase: 'failed', error: errorMessage, } : current, ); }, }); const bigFishSession = bigFishFlow.session; 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); setMatch3DError(null); setStreamingMatch3DReplyText(''); setIsStreamingMatch3DReply(false); await match3dFlow.openWorkspace(); }, [ match3dFlow, setIsStreamingMatch3DReply, setMatch3DError, setMatch3DSession, setStreamingMatch3DReplyText, ]); const openPuzzleAgentWorkspace = useCallback(async () => { setPuzzleRun(null); setPuzzleOperation(null); setPuzzleGenerationState(null); setPuzzleFormDraftPayload(null); puzzleFlow.setSession(null); puzzleFlow.setError(null); puzzleFlow.setStreamingReplyText(''); puzzleFlow.setIsStreamingReply(false); enterCreateTab(); setShowCreationTypeModal(false); setSelectionStage('puzzle-agent-workspace'); }, [enterCreateTab, puzzleFlow, setSelectionStage]); const createPuzzleDraftFromForm = useCallback( async (payload: CreatePuzzleAgentSessionRequest) => { setPuzzleFormDraftPayload(payload); const nextSession = await puzzleFlow.openWorkspace(payload); if (!nextSession) { return; } await puzzleFlow.executeAction( buildPuzzleCompileActionFromFormPayload(payload), nextSession, ); }, [puzzleFlow], ); 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); setMatch3DError(null); setStreamingMatch3DReplyText(''); setIsStreamingMatch3DReply(false); setPuzzleOperation(null); setPuzzleWorks([]); setSelectedPuzzleDetail(null); setPuzzleRuntimeReturnStage('puzzle-gallery-detail'); setPuzzleRun(null); setPuzzleGenerationState(null); setIsPuzzleNextLevelGenerating(false); setPuzzleError(null); setDeletingCreationWorkId(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(() => { 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; 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', ); } }, [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, ) => { if (isPuzzleBusy) { return; } setIsPuzzleBusy(true); setPuzzleError(null); try { const item = detailItem ?? (await getPuzzleGalleryDetail(profileId)).item; const { run } = await startPuzzleRun({ profileId: item.profileId }); setSelectedPuzzleDetail(item); setPuzzleRun(run); setPuzzleRuntimeReturnStage(returnStage); setSelectionStage('puzzle-runtime'); pushAppHistoryPath( buildPublicWorkStagePath( 'puzzle-runtime', buildPuzzlePublicWorkCode(item.profileId), ), ); } catch (error) { const message = resolvePuzzleErrorMessage(error, '启动拼图玩法失败。'); setPuzzleError(message); if (mirrorErrorToPublicDetail) { setPublicWorkDetailError(message); } } finally { setIsPuzzleBusy(false); } }, [ isPuzzleBusy, resolvePuzzleErrorMessage, setIsPuzzleBusy, setPuzzleError, 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 ?? '玩家', 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, publishReady: Boolean(puzzleSession?.resultPreview?.publishReady), } 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(() => { if (!isLocalPuzzleRun(puzzleRun)) { return; } 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 !== 'playing' ) { 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, ); } catch (error) { setPuzzleError( resolvePuzzleErrorMessage(error, '同步拼图失败状态失败。'), ); } }, [puzzleRun, resolvePuzzleErrorMessage, setPuzzleError]); const usePuzzleProp = useCallback( async (propKind: 'hint' | 'reference' | 'freezeTime') => { if ( !puzzleRun?.currentLevel || puzzleRun.currentLevel.status !== 'playing' ) { return null; } if (isLocalPuzzleRun(puzzleRun)) { const currentRun = puzzleRunRef.current ?? puzzleRun; if (!currentRun.currentLevel) { return null; } const nextRun = 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(); return nextRun; }, [platformBootstrap, puzzleRun], ); 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)); setIsPuzzleLeaderboardBusy(false); return; } void submitPuzzleLeaderboard(puzzleRun.runId, payload) .then(({ run }) => { setPuzzleRun((currentRun) => { if (!currentRun) { return currentRun; } return mergePuzzleServiceRuntimeState(currentRun, run); }); }) .catch((error) => { submittedPuzzleLeaderboardKeysRef.current.delete(submitKey); setPuzzleError( resolvePuzzleErrorMessage(error, '提交拼图排行榜失败。'), ); }) .finally(() => { setIsPuzzleLeaderboardBusy(false); }); }, [ authUi?.user?.displayName, puzzleRun, resolvePuzzleErrorMessage, setPuzzleError, ]); const advancePuzzleLevel = useCallback(async () => { if (!puzzleRun || isPuzzleBusy) { return; } const currentLevel = puzzleRun.currentLevel; if (!currentLevel || currentLevel.status !== 'cleared') { return; } setIsPuzzleBusy(true); setIsPuzzleNextLevelGenerating(true); setPuzzleError(null); try { const { run } = await advanceLocalPuzzleNextLevel({ run: puzzleRun, sourceSessionId: selectedPuzzleDetail?.sourceSessionId ?? puzzleSession?.sessionId ?? null, }); setPuzzleRun(run); } catch (error) { setPuzzleError(resolvePuzzleErrorMessage(error, '准备下一关失败。')); } finally { setIsPuzzleNextLevelGenerating(false); setIsPuzzleBusy(false); } }, [ isPuzzleBusy, puzzleRun, puzzleSession, resolvePuzzleErrorMessage, selectedPuzzleDetail, ]); const leaveAgentWorkspace = useCallback(() => { enterCreateTab(); 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; } runProtectedAction(() => { const confirmed = window.confirm( `确认删除作品《${entry.worldName}》吗?删除后会从你的作品列表和公开广场中移除。`, ); if (!confirmed) { return; } 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, runProtectedAction], ); const handleDeletePublishedWork = useCallback( (work: (typeof creationHubItems)[number]) => { if (deletingCreationWorkId) { return; } runProtectedAction(() => { const confirmed = window.confirm( `确认删除作品《${work.title}》吗?删除后会从你的作品列表和公开广场中移除。`, ); if (!confirmed) { return; } 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, runProtectedAction], ); const handleDeleteBigFishWork = useCallback( (work: BigFishWorkSummary) => { if (deletingCreationWorkId) { return; } runProtectedAction(() => { const confirmed = window.confirm( `确认删除作品《${work.title}》吗?删除后会从你的作品列表中移除。`, ); if (!confirmed) { return; } 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, resolveBigFishErrorMessage, runProtectedAction, ], ); const handleDeletePuzzleWork = useCallback( (work: PuzzleWorkSummary) => { if (deletingCreationWorkId) { return; } runProtectedAction(() => { const confirmed = window.confirm( `确认删除作品《${work.levelName}》吗?删除后会从你的作品列表和公开广场中移除。`, ); if (!confirmed) { return; } setDeletingCreationWorkId(work.workId); setPuzzleError(null); void deletePuzzleWork(work.profileId) .then((response) => { setPuzzleWorks(response.items); void refreshPuzzleGallery(); }) .catch((error) => { setPuzzleError( resolvePuzzleErrorMessage(error, '删除拼图作品失败。'), ); }) .finally(() => { setDeletingCreationWorkId(null); }); }); }, [ deletingCreationWorkId, refreshPuzzleGallery, resolvePuzzleErrorMessage, runProtectedAction, ], ); 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], ); 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 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); } }, [openPuzzleDetail, puzzleFlow, refreshPuzzleShelf, setPuzzleError], ); 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; } 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; } const launchEntry = selectedDetailEntry?.profileId === selectedPublicWorkDetail.profileId ? selectedDetailEntry : null; if (!launchEntry) { setPublicWorkDetailError('作品详情尚未读取完成。'); return; } runProtectedAction(() => { 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, 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; } 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 remixSelectedPublicWork = useCallback(() => { if (!selectedPublicWorkDetail) { return; } remixPublicWork(selectedPublicWorkDetail); }, [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 shouldSearchPuzzleFirst = upperKeyword.startsWith('PZ'); const shouldSearchWorkFirst = !shouldSearchUserIdFirst && !shouldSearchBigFishFirst && !shouldSearchPuzzleFirst && (upperKeyword.startsWith('CW') || /^\d{1,8}$/u.test(normalizedKeyword)); const shouldSearchUserFirst = shouldSearchUserIdFirst || upperKeyword.startsWith('SY') || (!shouldSearchWorkFirst && !shouldSearchBigFishFirst && !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)); }; try { if (shouldSearchUserIdFirst) { const user = await getPublicAuthUserById(normalizedKeyword); setSearchedPublicUser(user); return; } if (shouldSearchPuzzleFirst) { await tryOpenPuzzleGalleryEntry(); return; } if (shouldSearchBigFishFirst) { await tryOpenBigFishGalleryEntry(); 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, 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 === '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 ?? '', 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, }); }, [ openPuzzlePublicWorkDetail, openPublicWorkDetail, openRpgPublicWorkDetail, refreshBigFishGallery, resolveBigFishErrorMessage, ], ); useEffect(() => { const publicWorkCode = initialPublicWorkCode?.trim(); if ( !publicWorkCode || handledInitialPublicWorkCodeRef.current === publicWorkCode ) { return; } handledInitialPublicWorkCodeRef.current = publicWorkCode; void handlePublicCodeSearch(publicWorkCode); }, [handlePublicCodeSearch, initialPublicWorkCode]); const openBigFishDraft = useCallback( async (item: BigFishWorkSummary) => { setBigFishRun(null); const restoredSession = await bigFishFlow.restoreDraft( item.sourceSessionId, ); if (!restoredSession) { await refreshBigFishShelf().catch(() => undefined); } }, [bigFishFlow, refreshBigFishShelf], ); useEffect(() => { if (selectionStage === 'platform') { if (isBigFishCreationVisible) { void refreshBigFishGallery(); } void refreshPuzzleGallery(); } }, [ isBigFishCreationVisible, refreshBigFishGallery, refreshPuzzleGallery, selectionStage, ]); useEffect(() => { if ( (platformBootstrap.platformTab === 'create' || selectionStage === 'platform') && platformBootstrap.canReadProtectedData ) { void refreshPuzzleShelf(); } }, [ platformBootstrap.canReadProtectedData, platformBootstrap.platformTab, 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 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 } puzzleItems={puzzleWorks} onOpenPuzzleDetail={(item) => { runProtectedAction(() => { void openPuzzleDraft(item); }); }} onDeletePuzzle={(item) => { handleDeletePuzzleWork(item); }} /> ); return ( <> {selectionStage === 'platform' && ( { 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; } 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'); }} onStart={startSelectedPublicWork} onRemix={remixSelectedPublicWork} /> )} {selectionStage === 'detail' && ( {detailNavigation.isDetailLoading || !selectedDetailEntry ? (
{detailNavigation.detailError || '正在读取作品详情...'}
) : selectedDetailEntry.visibility !== 'draft' ? ( { detailNavigation.setDetailError(null); clearSelectedPublicWorkAuthor(); entryNavigation.backToPlatformHome(); }} onStart={handleStartSelectedWorld} onRemix={() => { remixPublicWork( mapRpgGalleryCardToPublicWorkDetail(selectedDetailEntry), ); }} /> ) : ( { 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'); }} /> )} {selectionStage === 'puzzle-agent-workspace' && ( } > { void submitPuzzleMessage(payload); }} onExecuteAction={(payload) => { void executePuzzleAction(payload); }} initialFormPayload={puzzleFormDraftPayload} onCreateFromForm={(payload) => { void createPuzzleDraftFromForm(payload); }} /> )} {selectionStage === 'puzzle-generating' && ( } > { setSelectionStage('puzzle-agent-workspace'); }} onRetry={() => { void executePuzzleAction( buildPuzzleCompileActionFromFormPayload( puzzleFormDraftPayload, ), ); }} onInterrupt={undefined} backLabel="返回创作中心" settingActionLabel={null} retryLabel="重新生成草稿" settingTitle="当前拼图信息" settingDescription={null} progressTitle="拼图草稿生成进度" activeBadgeLabel="草稿生成中" pausedBadgeLabel="草稿生成已暂停" idleBadgeLabel="等待返回工作区" /> )} {selectionStage === 'puzzle-result' && puzzleSession?.draft && ( } > { 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); }} onSwapPieces={(payload) => { void swapPuzzlePiecesInRun(payload); }} onDragPiece={(payload) => { void dragPuzzlePiece(payload); }} onAdvanceNextLevel={() => { void advancePuzzleLevel(); }} 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 { await enterWorldCoordinator.publishCurrentResult(); } 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(); }); }} /> {(searchedPublicUser || publicSearchError) && (
公开编号搜索
{publicSearchError ? '未找到结果' : '命中用户'}
{publicSearchError ? (
{publicSearchError}
) : searchedPublicUser ? (
{searchedPublicUser.displayName}
陶泥号 {searchedPublicUser.publicUserCode}
) : null}
)}
); } export default PlatformEntryFlowShellImpl;