import { AnimatePresence, motion } from 'motion/react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import type { CustomWorldAgentActionRequest, CustomWorldAgentOperationRecord, CustomWorldAgentSessionSnapshot, CustomWorldWorkSummary, SendCustomWorldAgentMessageRequest, } from '../../../packages/shared/src/contracts/customWorldAgent'; import { buildCustomWorldPlayableCharacters } from '../../data/characterPresets'; import { resolveCustomWorldCampSceneImage } from '../../data/customWorldVisuals'; import { createCustomWorldAgentSession, executeCustomWorldAgentAction, getCustomWorldAgentOperation, getCustomWorldAgentSession, listCustomWorldWorks, sendCustomWorldAgentMessage, } from '../../services/aiService'; import { clearCustomWorldAgentUiState, readCustomWorldAgentUiState, writeCustomWorldAgentUiState, } from '../../services/customWorldAgentUiState'; import { detectCustomWorldThemeMode } from '../../services/customWorldTheme'; import { listCustomWorldLibrary } from '../../services/storageService'; import { type CustomWorldProfile, type GameState } from '../../types'; import { CHROME_ICONS, CUSTOM_WORLD_THEME_ICONS, getNineSliceStyle, UI_CHROME, } from '../../uiAssets'; import { CustomWorldAgentWorkspace } from '../custom-world-agent/CustomWorldAgentWorkspace'; import { CustomWorldCreationHub } from '../custom-world-home/CustomWorldCreationHub'; import { DeveloperTeamModal } from '../DeveloperTeamModal'; import { PixelIcon } from '../PixelIcon'; export type SelectionStage = | 'start' | 'world' | 'custom-world-home' | 'custom-world-agent'; type PreGameSelectionFlowProps = { selectionStage: SelectionStage; setSelectionStage: (stage: SelectionStage) => void; gameState: GameState; hasSavedGame: boolean; handleContinueGame: () => void; handleStartNewGame: () => void; handleCustomWorldSelect: (customWorldProfile: CustomWorldProfile) => void; }; const DEVELOPER_TEAM_MESSAGE = '\u7a0b\u7b56\u7f8e\uff1a\u53d9\u4e16AI \u5305\u4ef2\u822a\n\u5408\u4f5c\u8bf7\u8054\u7cfb\u5fae\u4fe1\uff1abzh253518756'; const START_SCREEN_CONTACTS = [ { label: 'QQ群', value: '1094580241' }, { label: '微信', value: 'bzh253518756' }, ] as const; function createOperationErrorBanner( message: string, ): CustomWorldAgentOperationRecord { return { operationId: `operation-error-${Date.now()}`, type: 'process_message', status: 'failed', phaseLabel: '处理失败', phaseDetail: message, progress: 100, error: message, }; } export function PreGameSelectionFlow({ selectionStage, setSelectionStage, gameState, hasSavedGame, handleContinueGame, handleStartNewGame, handleCustomWorldSelect, }: PreGameSelectionFlowProps) { const [savedCustomWorldProfiles, setSavedCustomWorldProfiles] = useState< CustomWorldProfile[] >([]); const [customWorldWorks, setCustomWorldWorks] = useState< CustomWorldWorkSummary[] >([]); const [isLoadingCustomWorldWorks, setIsLoadingCustomWorldWorks] = useState(false); const [customWorldWorksError, setCustomWorldWorksError] = useState(null); const [showDeveloperTeamModal, setShowDeveloperTeamModal] = useState(false); const [isCreatingCustomWorldWork, setIsCreatingCustomWorldWork] = useState(false); const [isRestoringAgentSession, setIsRestoringAgentSession] = useState(false); const [activeCustomWorldAgentSessionId, setActiveCustomWorldAgentSessionId] = useState(null); const [ activeCustomWorldAgentOperationId, setActiveCustomWorldAgentOperationId, ] = useState(null); const [activeCustomWorldAgentSession, setActiveCustomWorldAgentSession] = useState(null); const [activeCustomWorldAgentOperation, setActiveCustomWorldAgentOperation] = useState(null); const clearOperationTimeoutRef = useRef(null); const savedCustomWorldCards = useMemo( () => savedCustomWorldProfiles.map((profile) => { const themeMode = detectCustomWorldThemeMode(profile); const leadCharacter = buildCustomWorldPlayableCharacters(profile)[0] ?? null; return { id: profile.id, profile, texture: UI_CHROME.panel, sceneImage: resolveCustomWorldCampSceneImage(profile) ?? '', featurePortrait: leadCharacter?.portrait ?? '', featureIcon: themeMode === 'martial' ? CUSTOM_WORLD_THEME_ICONS.martial : themeMode === 'arcane' ? CUSTOM_WORLD_THEME_ICONS.arcane : CHROME_ICONS.refreshOptions, accentLabel: '自定义世界', }; }), [savedCustomWorldProfiles], ); const refreshCustomWorldLibrary = useCallback(async () => { try { setSavedCustomWorldProfiles(await listCustomWorldLibrary()); } catch (error) { console.warn( '[PreGameSelectionFlow] failed to load custom world library', error, ); } }, []); const refreshCustomWorldWorks = useCallback(async () => { setIsLoadingCustomWorldWorks(true); try { setCustomWorldWorks(await listCustomWorldWorks()); setCustomWorldWorksError(null); } catch (error) { setCustomWorldWorksError( error instanceof Error ? error.message : '读取创作作品失败。', ); } finally { setIsLoadingCustomWorldWorks(false); } }, []); const refreshCustomWorldHomeData = useCallback(async () => { await Promise.all([refreshCustomWorldLibrary(), refreshCustomWorldWorks()]); }, [refreshCustomWorldLibrary, refreshCustomWorldWorks]); const syncCustomWorldAgentUiState = useCallback( (sessionId: string | null, operationId: string | null) => { setActiveCustomWorldAgentSessionId(sessionId); setActiveCustomWorldAgentOperationId(operationId); writeCustomWorldAgentUiState({ activeSessionId: sessionId, activeOperationId: operationId, }); }, [], ); const scheduleClearOperationBanner = useCallback(() => { if (clearOperationTimeoutRef.current) { window.clearTimeout(clearOperationTimeoutRef.current); } clearOperationTimeoutRef.current = window.setTimeout(() => { setActiveCustomWorldAgentOperation(null); setActiveCustomWorldAgentOperationId(null); writeCustomWorldAgentUiState({ activeSessionId: activeCustomWorldAgentSessionId, activeOperationId: null, }); clearOperationTimeoutRef.current = null; }, 1400); }, [activeCustomWorldAgentSessionId]); const refreshActiveCustomWorldAgentSession = useCallback( async (sessionId: string) => { const session = await getCustomWorldAgentSession(sessionId); setActiveCustomWorldAgentSession(session); return session; }, [], ); const loadCustomWorldAgentSession = useCallback( async (sessionId: string, operationId?: string | null) => { setIsRestoringAgentSession(true); setActiveCustomWorldAgentSession(null); try { const session = await getCustomWorldAgentSession(sessionId); setActiveCustomWorldAgentSession(session); setActiveCustomWorldAgentOperation(null); syncCustomWorldAgentUiState(sessionId, operationId ?? null); setSelectionStage('custom-world-agent'); if (operationId) { try { const operation = await getCustomWorldAgentOperation( sessionId, operationId, ); setActiveCustomWorldAgentOperation(operation); } catch (error) { setActiveCustomWorldAgentOperation( createOperationErrorBanner( error instanceof Error ? error.message : '读取操作状态失败。', ), ); } } } catch (error) { clearCustomWorldAgentUiState(); syncCustomWorldAgentUiState(null, null); setSelectionStage('custom-world-home'); setCustomWorldWorksError( error instanceof Error ? error.message : '恢复共创会话失败。', ); } finally { setIsRestoringAgentSession(false); } }, [setSelectionStage, syncCustomWorldAgentUiState], ); const leaveCustomWorldAgentWorkspace = useCallback(async () => { clearCustomWorldAgentUiState(); syncCustomWorldAgentUiState(null, null); setActiveCustomWorldAgentSession(null); setActiveCustomWorldAgentOperation(null); setSelectionStage('custom-world-home'); await refreshCustomWorldHomeData(); }, [ refreshCustomWorldHomeData, setSelectionStage, syncCustomWorldAgentUiState, ]); useEffect(() => { void refreshCustomWorldHomeData(); const restoredState = readCustomWorldAgentUiState(); if (!gameState.worldType && restoredState.activeSessionId) { void loadCustomWorldAgentSession( restoredState.activeSessionId, restoredState.activeOperationId ?? null, ); } }, [ gameState.worldType, loadCustomWorldAgentSession, refreshCustomWorldHomeData, ]); useEffect(() => { if (!gameState.worldType && selectionStage === 'custom-world-home') { void refreshCustomWorldHomeData(); } }, [gameState.worldType, refreshCustomWorldHomeData, selectionStage]); useEffect(() => { if ( !activeCustomWorldAgentSessionId || !activeCustomWorldAgentOperationId || !activeCustomWorldAgentOperation || (activeCustomWorldAgentOperation.status !== 'queued' && activeCustomWorldAgentOperation.status !== 'running') ) { return; } const timeoutId = window.setTimeout(async () => { try { const operation = await getCustomWorldAgentOperation( activeCustomWorldAgentSessionId, activeCustomWorldAgentOperationId, ); setActiveCustomWorldAgentOperation(operation); if ( operation.status === 'completed' || operation.status === 'failed' ) { await refreshActiveCustomWorldAgentSession( activeCustomWorldAgentSessionId, ); await refreshCustomWorldWorks(); if (operation.status === 'completed') { scheduleClearOperationBanner(); } else { setActiveCustomWorldAgentOperationId(null); writeCustomWorldAgentUiState({ activeSessionId: activeCustomWorldAgentSessionId, activeOperationId: null, }); } } } catch (error) { setActiveCustomWorldAgentOperation( createOperationErrorBanner( error instanceof Error ? error.message : '读取操作状态失败。', ), ); } }, 500); return () => window.clearTimeout(timeoutId); }, [ activeCustomWorldAgentOperation, activeCustomWorldAgentOperationId, activeCustomWorldAgentSessionId, refreshActiveCustomWorldAgentSession, refreshCustomWorldWorks, scheduleClearOperationBanner, ]); useEffect( () => () => { if (clearOperationTimeoutRef.current) { window.clearTimeout(clearOperationTimeoutRef.current); } }, [], ); const openCustomWorldCreator = () => { setSelectionStage('custom-world-home'); }; const startNewGame = () => { handleStartNewGame(); clearCustomWorldAgentUiState(); syncCustomWorldAgentUiState(null, null); setActiveCustomWorldAgentSession(null); setActiveCustomWorldAgentOperation(null); setSelectionStage('world'); }; const handleCreateNewWork = async () => { if (isCreatingCustomWorldWork) { return; } setIsCreatingCustomWorldWork(true); setCustomWorldWorksError(null); try { const response = await createCustomWorldAgentSession({}); setActiveCustomWorldAgentSession(response.session); setActiveCustomWorldAgentOperation(null); syncCustomWorldAgentUiState(response.session.sessionId, null); setSelectionStage('custom-world-agent'); await refreshCustomWorldWorks(); } catch (error) { setCustomWorldWorksError( error instanceof Error ? error.message : '创建共创会话失败。', ); } finally { setIsCreatingCustomWorldWork(false); } }; const handleResumeDraft = async (sessionId: string) => { await loadCustomWorldAgentSession(sessionId, null); }; const handleEnterPublished = async (profileId: string) => { let profile = savedCustomWorldProfiles.find((item) => item.id === profileId) ?? null; if (!profile) { const profiles = await listCustomWorldLibrary(); setSavedCustomWorldProfiles(profiles); profile = profiles.find((item) => item.id === profileId) ?? null; } if (!profile) { setCustomWorldWorksError('读取已发布世界失败。'); return; } handleCustomWorldSelect(profile); }; const handleSubmitCustomWorldAgentMessage = async ( payload: SendCustomWorldAgentMessageRequest, ) => { if (!activeCustomWorldAgentSessionId) { return; } try { const response = await sendCustomWorldAgentMessage( activeCustomWorldAgentSessionId, payload, ); setActiveCustomWorldAgentOperation(response.operation); syncCustomWorldAgentUiState( activeCustomWorldAgentSessionId, response.operation.operationId, ); await refreshActiveCustomWorldAgentSession(activeCustomWorldAgentSessionId); } catch (error) { setActiveCustomWorldAgentOperation( createOperationErrorBanner( error instanceof Error ? error.message : '发送共创消息失败。', ), ); } }; const handleExecuteCustomWorldAgentAction = async ( payload: CustomWorldAgentActionRequest, ) => { if (!activeCustomWorldAgentSessionId) { return; } try { const response = await executeCustomWorldAgentAction( activeCustomWorldAgentSessionId, payload, ); setActiveCustomWorldAgentOperation(response.operation); syncCustomWorldAgentUiState( activeCustomWorldAgentSessionId, response.operation.operationId, ); await refreshActiveCustomWorldAgentSession(activeCustomWorldAgentSessionId); } catch (error) { setActiveCustomWorldAgentOperation( createOperationErrorBanner( error instanceof Error ? error.message : '执行共创动作失败。', ), ); } }; const handleRefreshCustomWorldAgentSession = async () => { if (!activeCustomWorldAgentSessionId) { return; } try { await refreshActiveCustomWorldAgentSession(activeCustomWorldAgentSessionId); if (activeCustomWorldAgentOperationId) { const operation = await getCustomWorldAgentOperation( activeCustomWorldAgentSessionId, activeCustomWorldAgentOperationId, ); setActiveCustomWorldAgentOperation(operation); } } catch (error) { setActiveCustomWorldAgentOperation( createOperationErrorBanner( error instanceof Error ? error.message : '刷新会话失败。', ), ); } }; return ( <> {!gameState.worldType && selectionStage === 'start' && (
{hasSavedGame ? ( ) : null}
联系方式
{START_SCREEN_CONTACTS.map((contact) => (
{contact.label} {contact.value}
))}
)} {!gameState.worldType && selectionStage === 'world' && (
自定义世界
{savedCustomWorldCards.map((world) => ( ))}
)} {!gameState.worldType && selectionStage === 'custom-world-home' && ( setSelectionStage('world')} onRetry={() => { void refreshCustomWorldHomeData(); }} onCreateNew={() => { void handleCreateNewWork(); }} onResumeDraft={(sessionId) => { void handleResumeDraft(sessionId); }} onEnterPublished={(profileId) => { void handleEnterPublished(profileId); }} /> )} {!gameState.worldType && selectionStage === 'custom-world-agent' && ( { void leaveCustomWorldAgentWorkspace(); }} onRefresh={() => { void handleRefreshCustomWorldAgentSession(); }} onSubmitMessage={(payload) => { void handleSubmitCustomWorldAgentMessage(payload); }} onExecuteAction={(payload) => { void handleExecuteCustomWorldAgentAction(payload); }} /> )}
setShowDeveloperTeamModal(false)} /> ); }