import { AnimatePresence, motion } from 'motion/react'; import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState, } from 'react'; import type { CustomWorldAgentActionRequest, CustomWorldAgentMessage, CustomWorldAgentOperationRecord, CustomWorldAgentSessionSnapshot, CustomWorldWorkSummary, SendCustomWorldAgentMessageRequest, } from '../../../packages/shared/src/contracts/customWorldAgent'; import type { CustomWorldGalleryCard, CustomWorldLibraryEntry, PlatformBrowseHistoryEntry, PlatformBrowseHistoryWriteEntry, ProfileDashboardSummary, ProfileSaveArchiveSummary, } from '../../../packages/shared/src/contracts/runtime'; import { buildCustomWorldPlayableCharacters } from '../../data/characterPresets'; import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes'; import { createCustomWorldAgentSession, executeCustomWorldAgentAction, getCustomWorldAgentOperation, getCustomWorldAgentSession, listCustomWorldWorks, streamCustomWorldAgentMessage, } from '../../services/aiService'; import { buildCustomWorldProfileFromAgentDraft } from '../../services/customWorldAgentDraftResult'; import { buildAgentDraftFoundationAnchorEntries, buildAgentDraftFoundationGenerationProgress, buildAgentDraftFoundationSettingText, isDraftFoundationOperation, isDraftFoundationOperationRunning, } from '../../services/customWorldAgentGenerationProgress'; import { readCustomWorldAgentUiState, writeCustomWorldAgentUiState, } from '../../services/customWorldAgentUiState'; import { buildCustomWorldCreatorIntentFoundationText } from '../../services/customWorldCreatorIntent'; import { deleteCustomWorldProfile, getCustomWorldGalleryDetail, getProfileDashboard, listCustomWorldGallery, listCustomWorldLibrary, listProfileBrowseHistory, listProfileSaveArchives, publishCustomWorldProfile, resumeProfileSaveArchive, unpublishCustomWorldProfile, upsertCustomWorldProfile, upsertProfileBrowseHistory, } from '../../services/storageService'; import { type CustomWorldProfile, type GameState } from '../../types'; import { useAuthUi } from '../auth/AuthUiContext'; import { CustomWorldCreationHub } from '../custom-world-home/CustomWorldCreationHub'; import { PlatformCreationTypeModal } from './PlatformCreationTypeModal'; import { type PlatformHomeTab, PlatformHomeView } from './PlatformHomeView'; import { PlatformWorldDetailView } from './PlatformWorldDetailView'; const CustomWorldGenerationView = lazy(async () => { const module = await import('../CustomWorldGenerationView'); return { default: module.CustomWorldGenerationView, }; }); const CustomWorldResultView = lazy(async () => { const module = await import('../CustomWorldResultView'); return { default: module.CustomWorldResultView, }; }); const CustomWorldAgentWorkspace = lazy(async () => { const module = await import( '../custom-world-agent/CustomWorldAgentWorkspace' ); return { default: module.CustomWorldAgentWorkspace, }; }); export type SelectionStage = | 'platform' | 'detail' | 'agent-workspace' | 'custom-world-generating' | 'custom-world-result'; type CustomWorldGenerationViewSource = 'agent-draft-foundation' | null; type CustomWorldResultViewSource = 'saved-profile' | 'agent-draft' | null; type CustomWorldAutoSaveState = 'idle' | 'saving' | 'saved' | 'error'; type SyncedAgentDraftResult = { session: CustomWorldAgentSessionSnapshot | null; profile: CustomWorldProfile | null; }; type PreGameSelectionFlowProps = { selectionStage: SelectionStage; setSelectionStage: (stage: SelectionStage) => void; gameState: GameState; hasSavedGame: boolean; savedSnapshot: HydratedSavedGameSnapshot | null; handleContinueGame: (snapshot?: HydratedSavedGameSnapshot | null) => void; handleStartNewGame: () => void; handleCustomWorldSelect: (customWorldProfile: CustomWorldProfile) => void; }; function resolveErrorMessage(error: unknown, fallback: string) { return error instanceof Error ? error.message : fallback; } function createFailedAgentOperation(params: { type: CustomWorldAgentOperationRecord['type']; phaseLabel: string; error: string; }): CustomWorldAgentOperationRecord { return { operationId: `local-failed-${Date.now()}`, type: params.type, status: 'failed', phaseLabel: params.phaseLabel, phaseDetail: params.error, progress: 100, error: params.error, }; } function buildOptimisticAgentMessage( payload: Pick, ): CustomWorldAgentMessage { return { ...payload, createdAt: new Date().toISOString(), relatedOperationId: null, }; } function normalizeAgentBackedProfile(profile: CustomWorldProfile) { const foundationText = buildCustomWorldCreatorIntentFoundationText( profile.creatorIntent, ).trim(); if (!foundationText || foundationText === profile.settingText.trim()) { return profile; } return { ...profile, settingText: foundationText, } satisfies CustomWorldProfile; } function stringifyAgentBackedProfile(profile: CustomWorldProfile) { return JSON.stringify(normalizeAgentBackedProfile(profile)); } function LazyPanelFallback({ label }: { label: string }) { return (
{label}
); } function buildCreationHubFallbackItems( entries: CustomWorldLibraryEntry[], ): CustomWorldWorkSummary[] { return entries .filter((entry) => entry.visibility === 'published') .map((entry) => ({ workId: `fallback:${entry.profileId}`, sourceType: 'published_profile', status: 'published', title: entry.worldName, subtitle: entry.subtitle || '已发布作品', summary: entry.summaryText || '继续补完这个世界的设定与游玩入口。', coverImageSrc: entry.coverImageSrc, coverRenderMode: 'image', coverCharacterImageSrcs: [], updatedAt: entry.updatedAt, publishedAt: entry.publishedAt, stage: null, stageLabel: '已发布', playableNpcCount: entry.playableNpcCount, landmarkCount: entry.landmarkCount, roleVisualReadyCount: 0, roleAnimationReadyCount: 0, roleAssetSummaryLabel: null, sessionId: null, profileId: entry.profileId, canResume: false, canEnterWorld: true, })); } export function PreGameSelectionFlow({ selectionStage, setSelectionStage, hasSavedGame, savedSnapshot, handleContinueGame, handleStartNewGame, handleCustomWorldSelect, }: PreGameSelectionFlowProps) { const authUi = useAuthUi(); const initialAgentUiStateRef = useRef(readCustomWorldAgentUiState()); const hasAppliedInitialAgentWorkspaceRef = useRef(false); const hasRequestedInitialAgentWorkspaceAuthRef = useRef(false); const [generatedCustomWorldProfile, setGeneratedCustomWorldProfile] = useState(null); const [savedCustomWorldEntries, setSavedCustomWorldEntries] = useState< CustomWorldLibraryEntry[] >([]); const [customWorldWorkEntries, setCustomWorldWorkEntries] = useState< CustomWorldWorkSummary[] >([]); const [publishedGalleryEntries, setPublishedGalleryEntries] = useState< CustomWorldGalleryCard[] >([]); const [historyEntries, setHistoryEntries] = useState< PlatformBrowseHistoryEntry[] >([]); const [saveEntries, setSaveEntries] = useState( [], ); const [platformTab, setPlatformTab] = useState('home'); const [selectedDetailEntry, setSelectedDetailEntry] = useState | null>(null); const [showCreationTypeModal, setShowCreationTypeModal] = useState(false); const [creationTypeError, setCreationTypeError] = useState( null, ); const [isCreatingAgentSession, setIsCreatingAgentSession] = useState(false); const [activeAgentSessionId, setActiveAgentSessionId] = useState< string | null >(() => initialAgentUiStateRef.current.activeSessionId ?? null); const [activeAgentOperationId, setActiveAgentOperationId] = useState< string | null >(() => initialAgentUiStateRef.current.activeOperationId ?? null); const [agentSession, setAgentSession] = useState(null); const [agentOperation, setAgentOperation] = useState(null); const [streamingAgentReplyText, setStreamingAgentReplyText] = useState(''); const [isStreamingAgentReply, setIsStreamingAgentReply] = useState(false); const [isLoadingAgentSession, setIsLoadingAgentSession] = useState(false); const [customWorldError, setCustomWorldError] = useState(null); const [platformError, setPlatformError] = useState(null); const [profileDashboard, setProfileDashboard] = useState(null); const [dashboardError, setDashboardError] = useState(null); const [_historyError, setHistoryError] = useState(null); const [saveError, setSaveError] = useState(null); const [detailError, setDetailError] = useState(null); const [isLoadingPlatform, setIsLoadingPlatform] = useState(false); const [isLoadingDashboard, setIsLoadingDashboard] = useState(false); const [isResumingSaveWorldKey, setIsResumingSaveWorldKey] = useState< string | null >(null); const [isDetailLoading, setIsDetailLoading] = useState(false); const [isMutatingDetail, setIsMutatingDetail] = useState(false); const [customWorldAutoSaveState, setCustomWorldAutoSaveState] = useState('idle'); const [customWorldAutoSaveError, setCustomWorldAutoSaveError] = useState< string | null >(null); const [customWorldGenerationViewSource, setCustomWorldGenerationViewSource] = useState(null); const [customWorldResultViewSource, setCustomWorldResultViewSource] = useState(null); const [agentDraftGenerationStartedAt, setAgentDraftGenerationStartedAt] = useState(null); const customWorldAutoSaveTimeoutRef = useRef(null); const lastAutoSavedProfileSignatureRef = useRef(null); const latestAutoSaveRequestIdRef = useRef(0); const latestAgentResultSyncSignatureRef = useRef(null); // 用户手动返回工作区后,先抑制自动重开结果页,避免刚退出又被 session 快照顶回去。 const isAgentDraftResultAutoOpenSuppressedRef = useRef(false); const isCustomWorldAutoSaveBusyRef = useRef(false); const platformTabBootstrapUserIdRef = useRef( undefined, ); const previewCustomWorldCharacters = useMemo( () => generatedCustomWorldProfile ? buildCustomWorldPlayableCharacters(generatedCustomWorldProfile) : [], [generatedCustomWorldProfile], ); const featuredGalleryEntries = useMemo( () => publishedGalleryEntries.slice(0, 6), [publishedGalleryEntries], ); const isAuthenticated = Boolean(authUi?.user); const runProtectedAction = useCallback( (action: () => void) => { if (!authUi?.requireAuth) { action(); return; } authUi.requireAuth(action); }, [authUi], ); const persistAgentUiState = useCallback( (nextSessionId: string | null, nextOperationId: string | null) => { setActiveAgentSessionId(nextSessionId); setActiveAgentOperationId(nextOperationId); writeCustomWorldAgentUiState({ activeSessionId: nextSessionId, activeOperationId: nextOperationId, }); }, [], ); const syncAgentSessionSnapshot = useCallback(async (sessionId: string) => { const nextSession = await getCustomWorldAgentSession(sessionId); setAgentSession(nextSession); return nextSession; }, []); const refreshProfileDashboard = useCallback(async () => { if (!authUi?.user) { setProfileDashboard(null); setDashboardError(null); setIsLoadingDashboard(false); return; } setIsLoadingDashboard(true); setDashboardError(null); try { setProfileDashboard(await getProfileDashboard()); } catch (error) { setDashboardError(resolveErrorMessage(error, '读取个人数据看板失败。')); } finally { setIsLoadingDashboard(false); } }, [authUi?.user]); const refreshCustomWorldWorks = useCallback(async () => { if (!authUi?.user) { setCustomWorldWorkEntries([]); return []; } const nextItems = await listCustomWorldWorks(); setCustomWorldWorkEntries(nextItems); return nextItems; }, [authUi?.user]); const appendBrowseHistoryEntry = useCallback( async (entry: PlatformBrowseHistoryWriteEntry) => { setHistoryError(null); try { const syncedEntries = await upsertProfileBrowseHistory(entry); setHistoryEntries(syncedEntries); } catch (error) { setHistoryError(resolveErrorMessage(error, '写入浏览历史失败。')); } }, [authUi?.user], ); useEffect(() => { const initialAgentSessionId = initialAgentUiStateRef.current.activeSessionId; if (!initialAgentSessionId || hasAppliedInitialAgentWorkspaceRef.current) { return; } setPlatformTab('create'); // URL 或 sessionStorage 中残留的共创工作区属于受保护入口, // 未登录时只允许先唤起登录弹窗,不能直接恢复会话请求。 if (!authUi?.user) { if (!hasRequestedInitialAgentWorkspaceAuthRef.current) { hasRequestedInitialAgentWorkspaceAuthRef.current = true; authUi?.openLoginModal?.(() => { setSelectionStage('agent-workspace'); }); } return; } hasAppliedInitialAgentWorkspaceRef.current = true; setSelectionStage('agent-workspace'); }, [authUi?.openLoginModal, authUi?.user, setSelectionStage]); useEffect(() => { if (!selectedDetailEntry) { return; } const nextOwnedEntry = savedCustomWorldEntries.find( (entry) => entry.ownerUserId === selectedDetailEntry.ownerUserId && entry.profileId === selectedDetailEntry.profileId, ); if (nextOwnedEntry && nextOwnedEntry !== selectedDetailEntry) { setSelectedDetailEntry(nextOwnedEntry); } }, [savedCustomWorldEntries, selectedDetailEntry]); useEffect(() => { let isActive = true; void (async () => { setHistoryEntries([]); setHistoryError(null); setSaveError(null); setIsLoadingPlatform(true); setPlatformError(null); setIsLoadingDashboard(isAuthenticated); setDashboardError(null); if (!isAuthenticated) { setSavedCustomWorldEntries([]); setCustomWorldWorkEntries([]); setSaveEntries([]); setProfileDashboard(null); } try { const [ libraryEntriesResult, workEntriesResult, galleryEntriesResult, dashboardResult, historyResult, saveArchivesResult, ] = await Promise.allSettled([ isAuthenticated ? listCustomWorldLibrary() : Promise.resolve([]), isAuthenticated ? listCustomWorldWorks() : Promise.resolve([]), listCustomWorldGallery(), isAuthenticated ? getProfileDashboard() : Promise.resolve(null), isAuthenticated ? listProfileBrowseHistory() : Promise.resolve([]), isAuthenticated ? listProfileSaveArchives() : Promise.resolve([]), ]); if (!isActive) { return; } if (libraryEntriesResult.status === 'fulfilled') { setSavedCustomWorldEntries(libraryEntriesResult.value); } else { setSavedCustomWorldEntries([]); } if (workEntriesResult.status === 'fulfilled') { setCustomWorldWorkEntries(workEntriesResult.value); } else { setCustomWorldWorkEntries([]); } if (galleryEntriesResult.status === 'fulfilled') { setPublishedGalleryEntries(galleryEntriesResult.value); } else { setPublishedGalleryEntries([]); } if ( (isAuthenticated && libraryEntriesResult.status === 'rejected') || (isAuthenticated && workEntriesResult.status === 'rejected') || galleryEntriesResult.status === 'rejected' ) { const platformFailure = libraryEntriesResult.status === 'rejected' ? libraryEntriesResult.reason : workEntriesResult.status === 'rejected' ? workEntriesResult.reason : galleryEntriesResult.status === 'rejected' ? galleryEntriesResult.reason : null; setPlatformError( resolveErrorMessage(platformFailure, '读取平台数据失败。'), ); } if (dashboardResult.status === 'fulfilled') { setProfileDashboard(dashboardResult.value); } else if (isAuthenticated) { setProfileDashboard(null); setDashboardError( resolveErrorMessage( dashboardResult.reason, '读取个人数据看板失败。', ), ); } if (historyResult.status === 'fulfilled') { setHistoryEntries(historyResult.value); } else if (isAuthenticated) { setHistoryError( resolveErrorMessage(historyResult.reason, '读取浏览历史失败。'), ); } if (saveArchivesResult.status === 'fulfilled') { setSaveEntries(saveArchivesResult.value); } else if (isAuthenticated) { setSaveEntries([]); setSaveError( resolveErrorMessage( saveArchivesResult.reason, '读取存档列表失败。', ), ); } const nextPlatformBootstrapUserId = authUi?.user?.id ?? null; if ( platformTabBootstrapUserIdRef.current !== nextPlatformBootstrapUserId ) { platformTabBootstrapUserIdRef.current = nextPlatformBootstrapUserId; if (!initialAgentUiStateRef.current.activeSessionId) { setPlatformTab( isAuthenticated && saveArchivesResult.status === 'fulfilled' && saveArchivesResult.value.length > 0 ? 'saves' : 'home', ); } } } finally { if (isActive) { setIsLoadingPlatform(false); setIsLoadingDashboard(false); } } })(); return () => { isActive = false; }; }, [authUi?.user, isAuthenticated]); useEffect(() => { if ( selectionStage === 'custom-world-result' && !generatedCustomWorldProfile ) { setSelectionStage(selectedDetailEntry ? 'detail' : 'platform'); } }, [ generatedCustomWorldProfile, selectedDetailEntry, selectionStage, setSelectionStage, ]); useEffect( () => () => { if (customWorldAutoSaveTimeoutRef.current !== null) { window.clearTimeout(customWorldAutoSaveTimeoutRef.current); } }, [], ); useEffect(() => { if (!activeAgentSessionId) { setAgentSession(null); setAgentOperation(null); setIsLoadingAgentSession(false); setStreamingAgentReplyText(''); setIsStreamingAgentReply(false); return; } if (!authUi?.user) { setAgentSession(null); setAgentOperation(null); setIsLoadingAgentSession(false); setStreamingAgentReplyText(''); setIsStreamingAgentReply(false); return; } let cancelled = false; setIsLoadingAgentSession(true); void syncAgentSessionSnapshot(activeAgentSessionId) .then(() => { if (!cancelled) { setCreationTypeError(null); } }) .catch((error) => { if (cancelled) { return; } setCreationTypeError( resolveErrorMessage(error, '读取 Agent 共创工作区失败。'), ); setAgentSession(null); setAgentOperation(null); setStreamingAgentReplyText(''); setIsStreamingAgentReply(false); persistAgentUiState(null, null); setPlatformTab('create'); setSelectionStage('platform'); }) .finally(() => { if (!cancelled) { setIsLoadingAgentSession(false); } }); return () => { cancelled = true; }; }, [ activeAgentSessionId, authUi?.user, persistAgentUiState, setSelectionStage, syncAgentSessionSnapshot, ]); useEffect(() => { if (!activeAgentSessionId || !activeAgentOperationId || !authUi?.user) { return; } let cancelled = false; const pollOperation = async () => { try { const nextOperation = await getCustomWorldAgentOperation( activeAgentSessionId, activeAgentOperationId, ); if (cancelled) { return; } setAgentOperation(nextOperation); if ( nextOperation.status === 'completed' || nextOperation.status === 'failed' ) { persistAgentUiState(activeAgentSessionId, null); await syncAgentSessionSnapshot(activeAgentSessionId).catch( () => null, ); } } catch (error) { if (cancelled) { return; } const errorMessage = resolveErrorMessage( error, '读取共创操作状态失败。', ); setAgentOperation( createFailedAgentOperation({ type: 'process_message', phaseLabel: '读取操作状态失败', error: errorMessage, }), ); persistAgentUiState(activeAgentSessionId, null); } }; void pollOperation(); const intervalId = window.setInterval(() => { void pollOperation(); }, 1200); return () => { cancelled = true; window.clearInterval(intervalId); }; }, [ activeAgentOperationId, activeAgentSessionId, authUi?.user, persistAgentUiState, syncAgentSessionSnapshot, ]); useEffect(() => { if ( !isDraftFoundationOperationRunning(agentOperation) || agentDraftGenerationStartedAt ) { return; } setAgentDraftGenerationStartedAt(Date.now()); }, [agentDraftGenerationStartedAt, agentOperation]); useEffect(() => { if ( selectionStage !== 'custom-world-generating' || customWorldGenerationViewSource !== 'agent-draft-foundation' || !isDraftFoundationOperation(agentOperation) || agentOperation.status !== 'completed' ) { return; } let cancelled = false; const timeoutId = window.setTimeout(() => { void (async () => { const latestSession = activeAgentSessionId ? await syncAgentSessionSnapshot(activeAgentSessionId).catch( () => null, ) : agentSession; if (cancelled) { return; } const draftResultProfile = buildCustomWorldProfileFromAgentDraft( latestSession ?? agentSession, ); if (!draftResultProfile) { setAgentDraftGenerationStartedAt(null); setCustomWorldGenerationViewSource(null); setSelectionStage('agent-workspace'); return; } setGeneratedCustomWorldProfile( normalizeAgentBackedProfile(draftResultProfile), ); setAgentDraftGenerationStartedAt(null); setCustomWorldGenerationViewSource(null); setCustomWorldResultViewSource('agent-draft'); setSelectionStage('custom-world-result'); })(); }, 900); return () => { cancelled = true; window.clearTimeout(timeoutId); }; }, [ activeAgentSessionId, agentOperation, customWorldGenerationViewSource, agentSession, selectionStage, setSelectionStage, syncAgentSessionSnapshot, ]); const agentDraftSettingPreview = useMemo( () => buildAgentDraftFoundationSettingText(agentSession), [agentSession], ); const agentDraftAnchorPreviewEntries = useMemo( () => buildAgentDraftFoundationAnchorEntries(agentSession), [agentSession], ); const agentDraftResultProfile = useMemo( () => buildCustomWorldProfileFromAgentDraft(agentSession), [agentSession], ); const shouldAutoOpenAgentDraftResult = useMemo( () => Boolean( agentDraftResultProfile && agentSession && (agentSession.stage === 'object_refining' || agentSession.stage === 'visual_refining' || agentSession.stage === 'long_tail_review' || agentSession.stage === 'ready_to_publish' || agentSession.stage === 'published') && agentSession.draftCards.length > 0, ), [agentDraftResultProfile, agentSession], ); useEffect(() => { if (!shouldAutoOpenAgentDraftResult || !agentDraftResultProfile) { return; } if (isAgentDraftResultAutoOpenSuppressedRef.current) { return; } if (selectionStage === 'agent-workspace') { setGeneratedCustomWorldProfile(agentDraftResultProfile); setCustomWorldResultViewSource('agent-draft'); isAgentDraftResultAutoOpenSuppressedRef.current = false; setSelectionStage('custom-world-result'); return; } if ( selectionStage === 'custom-world-result' && !generatedCustomWorldProfile ) { setGeneratedCustomWorldProfile(agentDraftResultProfile); setCustomWorldResultViewSource('agent-draft'); isAgentDraftResultAutoOpenSuppressedRef.current = false; } }, [ agentDraftResultProfile, generatedCustomWorldProfile, isAgentDraftResultAutoOpenSuppressedRef, selectionStage, setSelectionStage, shouldAutoOpenAgentDraftResult, ]); const agentDraftGenerationProgress = useMemo( () => buildAgentDraftFoundationGenerationProgress( agentOperation, agentDraftGenerationStartedAt, ), [agentDraftGenerationStartedAt, agentOperation], ); const isAgentDraftGenerationView = customWorldGenerationViewSource === 'agent-draft-foundation'; const isAgentDraftResultView = customWorldResultViewSource === 'agent-draft'; const activeGenerationSettingText = agentDraftSettingPreview; const activeGenerationProgress = agentDraftGenerationProgress; const isActiveGenerationRunning = isDraftFoundationOperationRunning(agentOperation); const activeGenerationError = isDraftFoundationOperation(agentOperation) && agentOperation.status === 'failed' ? agentOperation.error || agentOperation.phaseDetail : null; const leaveCustomWorldResult = () => { setGeneratedCustomWorldProfile(null); setCustomWorldError(null); setCustomWorldAutoSaveError(null); setCustomWorldAutoSaveState('idle'); setCustomWorldGenerationViewSource(null); setCustomWorldResultViewSource(null); setSelectionStage( isAgentDraftResultView ? 'agent-workspace' : selectedDetailEntry ? 'detail' : 'platform', ); }; const openCreationTypePicker = () => { if (isCreatingAgentSession) { return; } if (!hasSavedGame) { handleStartNewGame(); } setCreationTypeError(null); setShowCreationTypeModal(true); }; const openRpgAgentWorkspace = async (seedText = '') => { if (isCreatingAgentSession) { return; } setIsCreatingAgentSession(true); setCreationTypeError(null); isAgentDraftResultAutoOpenSuppressedRef.current = false; try { const { session } = await createCustomWorldAgentSession( seedText ? { seedText } : {}, ); setAgentSession(session); setAgentOperation(null); setGeneratedCustomWorldProfile(null); setCustomWorldAutoSaveError(null); setCustomWorldAutoSaveState('idle'); setAgentDraftGenerationStartedAt(null); setCustomWorldGenerationViewSource(null); setCustomWorldResultViewSource(null); persistAgentUiState(session.sessionId, null); setShowCreationTypeModal(false); setPlatformTab('create'); setSelectionStage('agent-workspace'); } catch (error) { setCreationTypeError(resolveErrorMessage(error, '开启共创工作台失败。')); } finally { setIsCreatingAgentSession(false); } }; const submitAgentMessage = async ( payload: SendCustomWorldAgentMessageRequest, ) => { if (!activeAgentSessionId) { return; } const optimisticUserMessage = buildOptimisticAgentMessage({ id: payload.clientMessageId, role: 'user', kind: 'chat', text: payload.text.trim(), }); setAgentOperation(null); persistAgentUiState(activeAgentSessionId, null); setStreamingAgentReplyText(''); setIsStreamingAgentReply(true); setAgentSession((current) => current ? { ...current, messages: [...current.messages, optimisticUserMessage], updatedAt: optimisticUserMessage.createdAt, } : current, ); try { const nextSession = await streamCustomWorldAgentMessage( activeAgentSessionId, payload, { onUpdate: (text) => { setStreamingAgentReplyText(text); }, }, ); setAgentSession(nextSession); setAgentOperation(null); setStreamingAgentReplyText(''); } catch (error) { const errorMessage = resolveErrorMessage(error, '发送共创消息失败。'); setAgentSession((current) => current ? { ...current, messages: [ ...current.messages, buildOptimisticAgentMessage({ id: `message-error-${Date.now()}`, role: 'assistant', kind: 'warning', text: errorMessage, }), ], updatedAt: new Date().toISOString(), } : current, ); setStreamingAgentReplyText(''); persistAgentUiState(activeAgentSessionId, null); } finally { setIsStreamingAgentReply(false); } }; const executeAgentAction = async (payload: CustomWorldAgentActionRequest) => { if (!activeAgentSessionId) { return; } const isDraftFoundationAction = payload.action === 'draft_foundation'; if (isDraftFoundationAction) { isAgentDraftResultAutoOpenSuppressedRef.current = false; setGeneratedCustomWorldProfile(null); setCustomWorldError(null); setCustomWorldAutoSaveError(null); setCustomWorldAutoSaveState('idle'); setCustomWorldGenerationViewSource('agent-draft-foundation'); setCustomWorldResultViewSource(null); setAgentDraftGenerationStartedAt(Date.now()); setSelectionStage('custom-world-generating'); } try { const { operation } = await executeCustomWorldAgentAction( activeAgentSessionId, payload, ); setAgentOperation(operation); persistAgentUiState(activeAgentSessionId, operation.operationId); } catch (error) { const errorMessage = resolveErrorMessage(error, '执行共创操作失败。'); setAgentOperation( createFailedAgentOperation({ type: payload.action === 'draft_foundation' ? 'draft_foundation' : payload.action, phaseLabel: '执行操作失败', error: errorMessage, }), ); persistAgentUiState(activeAgentSessionId, null); } }; const leaveAgentWorkspace = () => { setPlatformTab('create'); setAgentOperation(null); setStreamingAgentReplyText(''); setIsStreamingAgentReply(false); setGeneratedCustomWorldProfile(null); setCustomWorldAutoSaveError(null); setCustomWorldAutoSaveState('idle'); setAgentDraftGenerationStartedAt(null); setCustomWorldGenerationViewSource(null); setCustomWorldResultViewSource(null); persistAgentUiState(activeAgentSessionId, null); setSelectionStage('platform'); }; const leaveAgentDraftGeneration = () => { if (isDraftFoundationOperationRunning(agentOperation)) { return; } setAgentDraftGenerationStartedAt(null); setCustomWorldGenerationViewSource(null); setSelectionStage('agent-workspace'); }; const leaveAgentDraftResult = () => { isAgentDraftResultAutoOpenSuppressedRef.current = true; setGeneratedCustomWorldProfile(null); setCustomWorldError(null); setCustomWorldAutoSaveError(null); setCustomWorldAutoSaveState('idle'); setCustomWorldGenerationViewSource(null); setCustomWorldResultViewSource(null); setPlatformTab('create'); setSelectionStage('platform'); }; const retryAgentDraftGeneration = () => { void executeAgentAction({ action: 'draft_foundation', }); }; const openCustomWorldCreator = () => { openCreationTypePicker(); }; const openLibraryDetail = useCallback( (entry: CustomWorldLibraryEntry) => { if (entry.visibility === 'published') { void appendBrowseHistoryEntry({ ownerUserId: entry.ownerUserId, profileId: entry.profileId, worldName: entry.worldName, subtitle: entry.subtitle, summaryText: entry.summaryText, coverImageSrc: entry.coverImageSrc, themeMode: entry.themeMode, authorDisplayName: entry.authorDisplayName, }); } setSelectedDetailEntry(entry); setDetailError(null); setSelectionStage('detail'); }, [appendBrowseHistoryEntry, setSelectionStage], ); const handleOpenCreationWork = useCallback( async (work: CustomWorldWorkSummary) => { if (work.status === 'draft' && work.sessionId) { persistAgentUiState(work.sessionId, null); setCustomWorldError(null); setCustomWorldAutoSaveError(null); setCustomWorldAutoSaveState('idle'); setCustomWorldGenerationViewSource(null); const shouldOpenAgentWorkspace = work.playableNpcCount <= 0 && work.landmarkCount <= 0; if (shouldOpenAgentWorkspace) { // 仅八锚点未整理成底稿时才恢复 Agent 对话工作区。 isAgentDraftResultAutoOpenSuppressedRef.current = true; setGeneratedCustomWorldProfile(null); setCustomWorldResultViewSource(null); setPlatformTab('create'); setSelectionStage('agent-workspace'); return; } isAgentDraftResultAutoOpenSuppressedRef.current = false; const latestSession = await syncAgentSessionSnapshot(work.sessionId); const nextProfile = buildCustomWorldProfileFromAgentDraft(latestSession); if (!nextProfile) { setPlatformError('当前草稿还没有可编辑的结果页数据,请先继续补齐锚点。'); setPlatformTab('create'); setSelectionStage('agent-workspace'); return; } setGeneratedCustomWorldProfile(normalizeAgentBackedProfile(nextProfile)); setCustomWorldResultViewSource('agent-draft'); setPlatformTab('create'); setSelectionStage('custom-world-result'); return; } if (!work.profileId) { return; } try { let matchedEntry = savedCustomWorldEntries.find( (entry) => entry.profileId === work.profileId, ); if (!matchedEntry && authUi?.user) { const latestLibraryEntries = await listCustomWorldLibrary(); setSavedCustomWorldEntries(latestLibraryEntries); matchedEntry = latestLibraryEntries.find( (entry) => entry.profileId === work.profileId, ); } if (matchedEntry) { openLibraryDetail(matchedEntry); return; } setPlatformError('未找到对应作品,请刷新后重试。'); } catch (error) { setPlatformError(resolveErrorMessage(error, '读取作品详情失败。')); } }, [ authUi?.user, openLibraryDetail, persistAgentUiState, savedCustomWorldEntries, syncAgentSessionSnapshot, setSelectionStage, ], ); const openGalleryDetail = async (entry: CustomWorldGalleryCard) => { setSelectionStage('detail'); setIsDetailLoading(true); setDetailError(null); try { const detailEntry = await getCustomWorldGalleryDetail( entry.ownerUserId, entry.profileId, ); setSelectedDetailEntry(detailEntry); void appendBrowseHistoryEntry({ ownerUserId: detailEntry.ownerUserId, profileId: detailEntry.profileId, worldName: detailEntry.worldName, subtitle: detailEntry.subtitle, summaryText: detailEntry.summaryText, coverImageSrc: detailEntry.coverImageSrc, themeMode: detailEntry.themeMode, authorDisplayName: detailEntry.authorDisplayName, }); } catch (error) { setSelectedDetailEntry(null); setDetailError(resolveErrorMessage(error, '读取作品详情失败。')); } finally { setIsDetailLoading(false); } }; const handleResumeSaveEntry = useCallback( async (entry: ProfileSaveArchiveSummary) => { if (!authUi?.user || isResumingSaveWorldKey) { return; } setIsResumingSaveWorldKey(entry.worldKey); setSaveError(null); try { const resumedArchive = await resumeProfileSaveArchive(entry.worldKey); setSaveEntries((currentEntries) => currentEntries.map((currentEntry) => currentEntry.worldKey === resumedArchive.entry.worldKey ? resumedArchive.entry : currentEntry, ), ); handleContinueGame(resumedArchive.snapshot); } catch (error) { setSaveError(resolveErrorMessage(error, '恢复存档失败。')); } finally { setIsResumingSaveWorldKey(null); } }, [authUi?.user, handleContinueGame, isResumingSaveWorldKey], ); const saveGeneratedCustomWorld = useCallback( async (profile = generatedCustomWorldProfile) => { if (!profile) { return null; } const normalizedProfile = normalizeAgentBackedProfile(profile); const profileSignature = stringifyAgentBackedProfile(normalizedProfile); const requestId = latestAutoSaveRequestIdRef.current + 1; latestAutoSaveRequestIdRef.current = requestId; setCustomWorldAutoSaveState('saving'); setCustomWorldAutoSaveError(null); try { const mutation = await upsertCustomWorldProfile(normalizedProfile); if (latestAutoSaveRequestIdRef.current !== requestId) { return mutation; } lastAutoSavedProfileSignatureRef.current = profileSignature; setSavedCustomWorldEntries(mutation.entries); if (authUi?.user) { void refreshCustomWorldWorks().catch(() => {}); } setSelectedDetailEntry((current) => { if (!current || current.profileId === mutation.entry.profileId) { return mutation.entry; } return current; }); setCustomWorldAutoSaveState('saved'); setCustomWorldAutoSaveError(null); return mutation; } catch (error) { if (latestAutoSaveRequestIdRef.current !== requestId) { return null; } setCustomWorldAutoSaveState('error'); setCustomWorldAutoSaveError( resolveErrorMessage(error, '保存自定义世界失败。'), ); return null; } }, [authUi?.user, generatedCustomWorldProfile, refreshCustomWorldWorks], ); const syncAgentDraftResultProfile = useCallback( async (profile: CustomWorldProfile) => { if (!activeAgentSessionId) { return { session: null, profile: null, } satisfies SyncedAgentDraftResult; } const normalizedProfile = normalizeAgentBackedProfile(profile); const profileSignature = stringifyAgentBackedProfile(normalizedProfile); const latestSessionProfileSignature = agentSession && buildCustomWorldProfileFromAgentDraft(agentSession) ? stringifyAgentBackedProfile( buildCustomWorldProfileFromAgentDraft(agentSession)!, ) : ''; if (latestSessionProfileSignature === profileSignature) { latestAgentResultSyncSignatureRef.current = profileSignature; return { session: agentSession, profile: normalizeAgentBackedProfile( buildCustomWorldProfileFromAgentDraft(agentSession) ?? profile, ), } satisfies SyncedAgentDraftResult; } if (latestAgentResultSyncSignatureRef.current === profileSignature) { return { session: agentSession, profile: normalizeAgentBackedProfile( buildCustomWorldProfileFromAgentDraft(agentSession) ?? profile, ), } satisfies SyncedAgentDraftResult; } const { operation } = await executeCustomWorldAgentAction( activeAgentSessionId, { action: 'sync_result_profile', profile: normalizedProfile as unknown as Record, }, ); setAgentOperation(operation); persistAgentUiState(activeAgentSessionId, operation.operationId); for (let attempt = 0; attempt < 60; attempt += 1) { const latestOperation = await getCustomWorldAgentOperation( activeAgentSessionId, operation.operationId, ); setAgentOperation(latestOperation); if (latestOperation.status === 'failed') { throw new Error( latestOperation.error || latestOperation.phaseDetail || '同步结果页世界快照失败。', ); } if (latestOperation.status === 'completed') { persistAgentUiState(activeAgentSessionId, null); const latestSession = await syncAgentSessionSnapshot( activeAgentSessionId, ); // 同步完成后统一从最新 session 重编译结果,保证结果页、作品库和进入世界吃同一份快照。 const latestProfile = normalizeAgentBackedProfile( buildCustomWorldProfileFromAgentDraft(latestSession) ?? profile, ); if (latestProfile) { setGeneratedCustomWorldProfile(latestProfile); } latestAgentResultSyncSignatureRef.current = profileSignature; return { session: latestSession, profile: latestProfile, } satisfies SyncedAgentDraftResult; } await new Promise((resolve) => window.setTimeout(resolve, 200)); } throw new Error('同步结果页世界快照超时。'); }, [ activeAgentSessionId, agentSession, persistAgentUiState, syncAgentSessionSnapshot, ], ); useEffect(() => { if (!generatedCustomWorldProfile) { setCustomWorldAutoSaveState('idle'); setCustomWorldAutoSaveError(null); lastAutoSavedProfileSignatureRef.current = null; latestAgentResultSyncSignatureRef.current = null; if (customWorldAutoSaveTimeoutRef.current !== null) { window.clearTimeout(customWorldAutoSaveTimeoutRef.current); customWorldAutoSaveTimeoutRef.current = null; } return; } if (selectionStage !== 'custom-world-result') { return; } if (isCustomWorldAutoSaveBusyRef.current) { return; } const nextSignature = stringifyAgentBackedProfile(generatedCustomWorldProfile); if (nextSignature === lastAutoSavedProfileSignatureRef.current) { return; } setCustomWorldAutoSaveState('saving'); if (customWorldAutoSaveTimeoutRef.current !== null) { window.clearTimeout(customWorldAutoSaveTimeoutRef.current); } const profileToSave = generatedCustomWorldProfile; customWorldAutoSaveTimeoutRef.current = window.setTimeout(() => { void (async () => { isCustomWorldAutoSaveBusyRef.current = true; try { let latestProfileToSave = normalizeAgentBackedProfile(profileToSave); if (isAgentDraftResultView) { const syncedResult = await syncAgentDraftResultProfile(profileToSave); // 作品库自动保存优先落同步后 session 重编译出的结果,避免继续保存旧的前端内存态。 latestProfileToSave = normalizeAgentBackedProfile( syncedResult.profile ?? profileToSave, ); } await saveGeneratedCustomWorld(latestProfileToSave); } catch (error) { setCustomWorldAutoSaveState('error'); setCustomWorldAutoSaveError( resolveErrorMessage(error, '保存自定义世界失败。'), ); } finally { isCustomWorldAutoSaveBusyRef.current = false; } })(); customWorldAutoSaveTimeoutRef.current = null; }, 600); return () => { if (customWorldAutoSaveTimeoutRef.current !== null) { window.clearTimeout(customWorldAutoSaveTimeoutRef.current); customWorldAutoSaveTimeoutRef.current = null; } }; }, [ generatedCustomWorldProfile, isAgentDraftResultView, saveGeneratedCustomWorld, selectionStage, syncAgentDraftResultProfile, ]); const openSavedCustomWorldEditor = ( entry: CustomWorldLibraryEntry, ) => { setSelectedDetailEntry(entry); const normalizedProfile = normalizeAgentBackedProfile(entry.profile); setGeneratedCustomWorldProfile(normalizedProfile); lastAutoSavedProfileSignatureRef.current = JSON.stringify(normalizedProfile); setCustomWorldAutoSaveState('saved'); setCustomWorldAutoSaveError(null); setCustomWorldError(null); setCustomWorldGenerationViewSource(null); setCustomWorldResultViewSource('saved-profile'); setSelectionStage('custom-world-result'); }; const handleStartSelectedWorld = () => { if (!selectedDetailEntry) { return; } runProtectedAction(() => { handleCustomWorldSelect(selectedDetailEntry.profile); }); }; const handlePublishSelectedWorld = async () => { if (!selectedDetailEntry || isMutatingDetail) { return; } setIsMutatingDetail(true); setDetailError(null); try { const mutation = await publishCustomWorldProfile( selectedDetailEntry.profileId, ); setSavedCustomWorldEntries(mutation.entries); await refreshCustomWorldWorks().catch(() => []); setSelectedDetailEntry(mutation.entry); setPublishedGalleryEntries(await listCustomWorldGallery()); } catch (error) { setDetailError(resolveErrorMessage(error, '发布自定义世界失败。')); } finally { setIsMutatingDetail(false); } }; const handleUnpublishSelectedWorld = async () => { if (!selectedDetailEntry || isMutatingDetail) { return; } setIsMutatingDetail(true); setDetailError(null); try { const mutation = await unpublishCustomWorldProfile( selectedDetailEntry.profileId, ); setSavedCustomWorldEntries(mutation.entries); await refreshCustomWorldWorks().catch(() => []); setSelectedDetailEntry(mutation.entry); setPublishedGalleryEntries(await listCustomWorldGallery()); } catch (error) { setDetailError(resolveErrorMessage(error, '下架自定义世界失败。')); } finally { setIsMutatingDetail(false); } }; const handleDeleteSelectedWorld = async () => { if (!selectedDetailEntry || isMutatingDetail) { return; } const confirmed = window.confirm( `确认删除作品《${selectedDetailEntry.worldName}》吗?删除后会从你的作品列表和公开广场中移除。`, ); if (!confirmed) { return; } setIsMutatingDetail(true); setDetailError(null); try { const entries = await deleteCustomWorldProfile( selectedDetailEntry.profileId, ); setSavedCustomWorldEntries(entries); await refreshCustomWorldWorks().catch(() => []); setSelectedDetailEntry(null); setPlatformTab('create'); setSelectionStage('platform'); setPublishedGalleryEntries(await listCustomWorldGallery()); } catch (error) { setDetailError(resolveErrorMessage(error, '删除自定义世界失败。')); } finally { setIsMutatingDetail(false); } }; const isSelectedWorldOwned = Boolean( selectedDetailEntry && savedCustomWorldEntries.some( (entry) => entry.ownerUserId === selectedDetailEntry.ownerUserId && entry.profileId === selectedDetailEntry.profileId, ), ); const resultViewError = customWorldAutoSaveError ?? customWorldError; const creationHubItems = customWorldWorkEntries.length > 0 ? customWorldWorkEntries : buildCreationHubFallbackItems(savedCustomWorldEntries); const creationHubContent = ( { setPlatformTab('home'); }} onRetry={() => { setPlatformError(null); void refreshCustomWorldWorks().catch((error) => { setPlatformError( resolveErrorMessage(error, '读取创作作品列表失败。'), ); }); }} onCreateNew={openCreationTypePicker} onOpenDraft={(item) => { runProtectedAction(() => { void handleOpenCreationWork(item); }); }} onEnterPublished={(profileId) => { runProtectedAction(() => { const matchedWork = creationHubItems.find( (entry) => entry.profileId === profileId, ); if (!matchedWork) { return; } void handleOpenCreationWork(matchedWork); }); }} /> ); return ( <> {selectionStage === 'platform' && ( { void handleResumeSaveEntry(entry); }} onOpenCreateWorld={openCustomWorldCreator} onOpenCreateTypePicker={openCreationTypePicker} onOpenGalleryDetail={(entry) => { runProtectedAction(() => { void openGalleryDetail(entry); }); }} onOpenLibraryDetail={(entry) => { runProtectedAction(() => { openLibraryDetail(entry); }); }} onOpenProfileDashboardCard={() => { if (dashboardError) { void refreshProfileDashboard(); } }} /> )} {selectionStage === 'detail' && ( {isDetailLoading || !selectedDetailEntry ? (
{detailError || '正在读取作品详情...'}
) : ( { setDetailError(null); setSelectionStage('platform'); }} onStartGame={handleStartSelectedWorld} onContinueEdit={ isSelectedWorldOwned ? () => { runProtectedAction(() => { openSavedCustomWorldEditor(selectedDetailEntry); }); } : null } onPublish={ selectedDetailEntry.visibility === 'draft' && isSelectedWorldOwned ? () => { runProtectedAction(() => { void handlePublishSelectedWorld(); }); } : null } onUnpublish={ selectedDetailEntry.visibility === 'published' && isSelectedWorldOwned ? () => { runProtectedAction(() => { void handleUnpublishSelectedWorld(); }); } : null } onDelete={ isSelectedWorldOwned ? () => { runProtectedAction(() => { void handleDeleteSelectedWorld(); }); } : null } /> )}
)} {selectionStage === 'agent-workspace' && ( } > {agentSession ? ( { void submitAgentMessage(payload); }} onExecuteAction={(payload) => { void executeAgentAction(payload); }} /> ) : (
{isLoadingAgentSession ? '正在准备 Agent 共创工作区...' : creationTypeError || '正在恢复创作工作区...'}
)}
)} {selectionStage === 'custom-world-generating' && ( } > )} {selectionStage === 'custom-world-result' && generatedCustomWorldProfile && ( } > { setGeneratedCustomWorldProfile( normalizeAgentBackedProfile(profile), ); }} onBack={ isAgentDraftResultView ? () => { void (async () => { const currentProfile = generatedCustomWorldProfile ?? buildCustomWorldProfileFromAgentDraft( agentSession, ); if (currentProfile && activeAgentSessionId) { await syncAgentDraftResultProfile(currentProfile); } leaveAgentDraftResult(); })().catch((error) => { setCustomWorldError( resolveErrorMessage( error, '返回创作前同步草稿失败。', ), ); }); } : leaveCustomWorldResult } onEditSetting={undefined} onRegenerate={undefined} onContinueExpand={undefined} onEnterWorld={() => { runProtectedAction(() => { void (async () => { if (!isAgentDraftResultView || !activeAgentSessionId) { handleCustomWorldSelect(generatedCustomWorldProfile); return; } const currentProfile = generatedCustomWorldProfile ?? buildCustomWorldProfileFromAgentDraft(agentSession); if (!currentProfile) { return; } const latestResult = await syncAgentDraftResultProfile( currentProfile, ); const latestProfile = normalizeAgentBackedProfile( buildCustomWorldProfileFromAgentDraft( latestResult.session ?? agentSession, ) ?? latestResult.profile ?? currentProfile, ); setGeneratedCustomWorldProfile(latestProfile); handleCustomWorldSelect(latestProfile); })().catch((error) => { setCustomWorldError( resolveErrorMessage(error, '进入世界前同步草稿失败。'), ); }); }); }} readOnly={false} compactAgentResultMode={isAgentDraftResultView} backLabel={isAgentDraftResultView ? '返回创作' : undefined} editActionLabel="继续调整设定" enterWorldActionLabel="进入世界" autoSaveState={customWorldAutoSaveState} /> )}
{ if (isCreatingAgentSession) { return; } setShowCreationTypeModal(false); }} onSelectRpg={() => { runProtectedAction(() => { void openRpgAgentWorkspace(); }); }} /> ); }