Files
Genarrative/src/components/game-shell/PreGameSelectionFlow.tsx
2026-04-21 10:30:12 +08:00

1921 lines
62 KiB
TypeScript

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, 'id' | 'role' | 'kind' | 'text'>,
): 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 (
<div className="flex h-full min-h-0 items-center justify-center">
<div className="platform-subpanel rounded-2xl px-5 py-4 text-sm text-[var(--platform-text-base)]">
{label}
</div>
</div>
);
}
function buildCreationHubFallbackItems(
entries: CustomWorldLibraryEntry<CustomWorldProfile>[],
): 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<CustomWorldProfile | null>(null);
const [savedCustomWorldEntries, setSavedCustomWorldEntries] = useState<
CustomWorldLibraryEntry<CustomWorldProfile>[]
>([]);
const [customWorldWorkEntries, setCustomWorldWorkEntries] = useState<
CustomWorldWorkSummary[]
>([]);
const [publishedGalleryEntries, setPublishedGalleryEntries] = useState<
CustomWorldGalleryCard[]
>([]);
const [historyEntries, setHistoryEntries] = useState<
PlatformBrowseHistoryEntry[]
>([]);
const [saveEntries, setSaveEntries] = useState<ProfileSaveArchiveSummary[]>(
[],
);
const [platformTab, setPlatformTab] = useState<PlatformHomeTab>('home');
const [selectedDetailEntry, setSelectedDetailEntry] =
useState<CustomWorldLibraryEntry<CustomWorldProfile> | null>(null);
const [showCreationTypeModal, setShowCreationTypeModal] = useState(false);
const [creationTypeError, setCreationTypeError] = useState<string | null>(
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<CustomWorldAgentSessionSnapshot | null>(null);
const [agentOperation, setAgentOperation] =
useState<CustomWorldAgentOperationRecord | null>(null);
const [streamingAgentReplyText, setStreamingAgentReplyText] = useState('');
const [isStreamingAgentReply, setIsStreamingAgentReply] = useState(false);
const [isLoadingAgentSession, setIsLoadingAgentSession] = useState(false);
const [customWorldError, setCustomWorldError] = useState<string | null>(null);
const [platformError, setPlatformError] = useState<string | null>(null);
const [profileDashboard, setProfileDashboard] =
useState<ProfileDashboardSummary | null>(null);
const [dashboardError, setDashboardError] = useState<string | null>(null);
const [_historyError, setHistoryError] = useState<string | null>(null);
const [saveError, setSaveError] = useState<string | null>(null);
const [detailError, setDetailError] = useState<string | null>(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<CustomWorldAutoSaveState>('idle');
const [customWorldAutoSaveError, setCustomWorldAutoSaveError] = useState<
string | null
>(null);
const [customWorldGenerationViewSource, setCustomWorldGenerationViewSource] =
useState<CustomWorldGenerationViewSource>(null);
const [customWorldResultViewSource, setCustomWorldResultViewSource] =
useState<CustomWorldResultViewSource>(null);
const [agentDraftGenerationStartedAt, setAgentDraftGenerationStartedAt] =
useState<number | null>(null);
const customWorldAutoSaveTimeoutRef = useRef<number | null>(null);
const lastAutoSavedProfileSignatureRef = useRef<string | null>(null);
const latestAutoSaveRequestIdRef = useRef(0);
const latestAgentResultSyncSignatureRef = useRef<string | null>(null);
// 用户手动返回工作区后,先抑制自动重开结果页,避免刚退出又被 session 快照顶回去。
const isAgentDraftResultAutoOpenSuppressedRef = useRef(false);
const isCustomWorldAutoSaveBusyRef = useRef(false);
const platformTabBootstrapUserIdRef = useRef<string | null | undefined>(
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<CustomWorldProfile>) => {
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<string, unknown>,
},
);
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<CustomWorldProfile>,
) => {
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 = (
<CustomWorldCreationHub
items={creationHubItems}
loading={isLoadingPlatform}
error={isLoadingPlatform ? null : (platformError ?? creationTypeError)}
onBack={() => {
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 (
<>
<AnimatePresence mode="wait">
{selectionStage === 'platform' && (
<motion.div
key="platform-home"
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -12 }}
className="flex h-full min-h-0 flex-col"
>
<PlatformHomeView
activeTab={platformTab}
onTabChange={setPlatformTab}
hasSavedGame={hasSavedGame}
savedSnapshot={savedSnapshot}
saveEntries={saveEntries}
saveError={saveError}
featuredEntries={featuredGalleryEntries}
latestEntries={publishedGalleryEntries}
myEntries={savedCustomWorldEntries}
historyEntries={historyEntries}
profileDashboard={profileDashboard}
isLoadingPlatform={isLoadingPlatform}
isLoadingDashboard={isLoadingDashboard}
isResumingSaveWorldKey={isResumingSaveWorldKey}
platformError={
isLoadingPlatform ? null : (platformError ?? creationTypeError)
}
dashboardError={isLoadingDashboard ? null : dashboardError}
createTabContent={creationHubContent}
onContinueGame={handleContinueGame}
onResumeSave={(entry) => {
void handleResumeSaveEntry(entry);
}}
onOpenCreateWorld={openCustomWorldCreator}
onOpenCreateTypePicker={openCreationTypePicker}
onOpenGalleryDetail={(entry) => {
runProtectedAction(() => {
void openGalleryDetail(entry);
});
}}
onOpenLibraryDetail={(entry) => {
runProtectedAction(() => {
openLibraryDetail(entry);
});
}}
onOpenProfileDashboardCard={() => {
if (dashboardError) {
void refreshProfileDashboard();
}
}}
/>
</motion.div>
)}
{selectionStage === 'detail' && (
<motion.div
key="platform-detail"
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -12 }}
className="flex h-full min-h-0 flex-col"
>
{isDetailLoading || !selectedDetailEntry ? (
<div className="flex h-full items-center justify-center">
<div className="platform-subpanel rounded-2xl px-5 py-4 text-sm text-[var(--platform-text-base)]">
{detailError || '正在读取作品详情...'}
</div>
</div>
) : (
<PlatformWorldDetailView
entry={selectedDetailEntry}
isMutating={isMutatingDetail}
error={detailError}
onBack={() => {
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
}
/>
)}
</motion.div>
)}
{selectionStage === 'agent-workspace' && (
<motion.div
key="agent-workspace"
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -12 }}
className="flex h-full min-h-0 flex-col"
>
<Suspense
fallback={
<LazyPanelFallback label="正在加载 Agent 共创工作区..." />
}
>
{agentSession ? (
<CustomWorldAgentWorkspace
session={agentSession}
activeOperation={agentOperation}
streamingReplyText={streamingAgentReplyText}
isStreamingReply={isStreamingAgentReply}
onBack={leaveAgentWorkspace}
onSubmitMessage={(payload) => {
void submitAgentMessage(payload);
}}
onExecuteAction={(payload) => {
void executeAgentAction(payload);
}}
/>
) : (
<div className="flex h-full items-center justify-center">
<div className="platform-subpanel rounded-2xl px-5 py-4 text-sm text-[var(--platform-text-base)]">
{isLoadingAgentSession
? '正在准备 Agent 共创工作区...'
: creationTypeError || '正在恢复创作工作区...'}
</div>
</div>
)}
</Suspense>
</motion.div>
)}
{selectionStage === 'custom-world-generating' && (
<motion.div
key="custom-world-generating"
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -12 }}
className="flex h-full min-h-0 flex-col"
>
<Suspense
fallback={<LazyPanelFallback label="正在加载世界生成面板..." />}
>
<CustomWorldGenerationView
settingText={activeGenerationSettingText}
anchorEntries={agentDraftAnchorPreviewEntries}
progress={activeGenerationProgress}
isGenerating={isActiveGenerationRunning}
error={activeGenerationError}
onBack={leaveAgentDraftGeneration}
onEditSetting={leaveAgentDraftGeneration}
onRetry={retryAgentDraftGeneration}
onInterrupt={undefined}
backLabel="返回工作区"
settingActionLabel={null}
retryLabel="重新生成草稿"
settingTitle="当前世界信息"
settingDescription={null}
progressTitle={
isAgentDraftGenerationView ? '世界草稿生成进度' : undefined
}
activeBadgeLabel={
isAgentDraftGenerationView ? '草稿编译中' : undefined
}
pausedBadgeLabel={
isAgentDraftGenerationView ? '草稿生成已暂停' : undefined
}
idleBadgeLabel={
isAgentDraftGenerationView ? '等待返回工作区' : undefined
}
/>
</Suspense>
</motion.div>
)}
{selectionStage === 'custom-world-result' &&
generatedCustomWorldProfile && (
<motion.div
key="custom-world-result"
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -12 }}
className="flex h-full min-h-0 flex-col"
>
<Suspense
fallback={<LazyPanelFallback label="正在加载世界编辑器..." />}
>
<CustomWorldResultView
profile={generatedCustomWorldProfile}
previewCharacters={previewCustomWorldCharacters}
isGenerating={false}
progress={0}
progressLabel=""
error={resultViewError}
onProfileChange={(profile) => {
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}
/>
</Suspense>
</motion.div>
)}
</AnimatePresence>
<PlatformCreationTypeModal
isOpen={showCreationTypeModal}
isBusy={isCreatingAgentSession}
error={creationTypeError}
onClose={() => {
if (isCreatingAgentSession) {
return;
}
setShowCreationTypeModal(false);
}}
onSelectRpg={() => {
runProtectedAction(() => {
void openRpgAgentWorkspace();
});
}}
/>
</>
);
}