1
This commit is contained in:
@@ -14,6 +14,7 @@ import type {
|
||||
CustomWorldAgentMessage,
|
||||
CustomWorldAgentOperationRecord,
|
||||
CustomWorldAgentSessionSnapshot,
|
||||
CustomWorldWorkSummary,
|
||||
SendCustomWorldAgentMessageRequest,
|
||||
} from '../../../packages/shared/src/contracts/customWorldAgent';
|
||||
import type {
|
||||
@@ -29,6 +30,7 @@ import {
|
||||
executeCustomWorldAgentAction,
|
||||
getCustomWorldAgentOperation,
|
||||
getCustomWorldAgentSession,
|
||||
listCustomWorldWorks,
|
||||
streamCustomWorldAgentMessage,
|
||||
} from '../../services/aiService';
|
||||
import { buildCustomWorldProfileFromAgentDraft } from '../../services/customWorldAgentDraftResult';
|
||||
@@ -69,6 +71,7 @@ import {
|
||||
} 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';
|
||||
@@ -107,6 +110,10 @@ 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;
|
||||
@@ -164,6 +171,10 @@ function normalizeAgentBackedProfile(profile: CustomWorldProfile) {
|
||||
} 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">
|
||||
@@ -174,6 +185,37 @@ function LazyPanelFallback({ label }: { label: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -191,6 +233,9 @@ export function PreGameSelectionFlow({
|
||||
const [savedCustomWorldEntries, setSavedCustomWorldEntries] = useState<
|
||||
CustomWorldLibraryEntry<CustomWorldProfile>[]
|
||||
>([]);
|
||||
const [customWorldWorkEntries, setCustomWorldWorkEntries] = useState<
|
||||
CustomWorldWorkSummary[]
|
||||
>([]);
|
||||
const [publishedGalleryEntries, setPublishedGalleryEntries] = useState<
|
||||
CustomWorldGalleryCard[]
|
||||
>([]);
|
||||
@@ -250,6 +295,10 @@ export function PreGameSelectionFlow({
|
||||
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,
|
||||
);
|
||||
@@ -318,6 +367,17 @@ export function PreGameSelectionFlow({
|
||||
}
|
||||
}, [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) => {
|
||||
const nextEntries = writePlatformBrowseHistory(authUi?.user, entry);
|
||||
@@ -380,6 +440,7 @@ export function PreGameSelectionFlow({
|
||||
setDashboardError(null);
|
||||
if (!isAuthenticated) {
|
||||
setSavedCustomWorldEntries([]);
|
||||
setCustomWorldWorkEntries([]);
|
||||
setSaveEntries([]);
|
||||
setProfileDashboard(null);
|
||||
}
|
||||
@@ -387,12 +448,14 @@ export function PreGameSelectionFlow({
|
||||
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
|
||||
@@ -423,6 +486,12 @@ export function PreGameSelectionFlow({
|
||||
setSavedCustomWorldEntries([]);
|
||||
}
|
||||
|
||||
if (workEntriesResult.status === 'fulfilled') {
|
||||
setCustomWorldWorkEntries(workEntriesResult.value);
|
||||
} else {
|
||||
setCustomWorldWorkEntries([]);
|
||||
}
|
||||
|
||||
if (galleryEntriesResult.status === 'fulfilled') {
|
||||
setPublishedGalleryEntries(galleryEntriesResult.value);
|
||||
} else {
|
||||
@@ -431,11 +500,14 @@ export function PreGameSelectionFlow({
|
||||
|
||||
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;
|
||||
@@ -742,9 +814,14 @@ export function PreGameSelectionFlow({
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAgentDraftResultAutoOpenSuppressedRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectionStage === 'agent-workspace') {
|
||||
setGeneratedCustomWorldProfile(agentDraftResultProfile);
|
||||
setCustomWorldResultViewSource('agent-draft');
|
||||
isAgentDraftResultAutoOpenSuppressedRef.current = false;
|
||||
setSelectionStage('custom-world-result');
|
||||
return;
|
||||
}
|
||||
@@ -755,10 +832,12 @@ export function PreGameSelectionFlow({
|
||||
) {
|
||||
setGeneratedCustomWorldProfile(agentDraftResultProfile);
|
||||
setCustomWorldResultViewSource('agent-draft');
|
||||
isAgentDraftResultAutoOpenSuppressedRef.current = false;
|
||||
}
|
||||
}, [
|
||||
agentDraftResultProfile,
|
||||
generatedCustomWorldProfile,
|
||||
isAgentDraftResultAutoOpenSuppressedRef,
|
||||
selectionStage,
|
||||
setSelectionStage,
|
||||
shouldAutoOpenAgentDraftResult,
|
||||
@@ -776,6 +855,8 @@ export function PreGameSelectionFlow({
|
||||
const isAgentDraftGenerationView =
|
||||
customWorldGenerationViewSource === 'agent-draft-foundation';
|
||||
const isAgentDraftResultView = customWorldResultViewSource === 'agent-draft';
|
||||
const isAgentDraftResultEditingFrozen =
|
||||
customWorldResultViewSource === 'agent-draft';
|
||||
const activeGenerationSettingText = agentDraftSettingPreview;
|
||||
const activeGenerationProgress = agentDraftGenerationProgress;
|
||||
const isActiveGenerationRunning =
|
||||
@@ -822,6 +903,7 @@ export function PreGameSelectionFlow({
|
||||
|
||||
setIsCreatingAgentSession(true);
|
||||
setCreationTypeError(null);
|
||||
isAgentDraftResultAutoOpenSuppressedRef.current = false;
|
||||
|
||||
try {
|
||||
const { session } = await createCustomWorldAgentSession(
|
||||
@@ -921,6 +1003,7 @@ export function PreGameSelectionFlow({
|
||||
const isDraftFoundationAction = payload.action === 'draft_foundation';
|
||||
|
||||
if (isDraftFoundationAction) {
|
||||
isAgentDraftResultAutoOpenSuppressedRef.current = false;
|
||||
setGeneratedCustomWorldProfile(null);
|
||||
setCustomWorldError(null);
|
||||
setCustomWorldAutoSaveError(null);
|
||||
@@ -980,14 +1063,14 @@ export function PreGameSelectionFlow({
|
||||
};
|
||||
|
||||
const leaveAgentDraftResult = () => {
|
||||
isAgentDraftResultAutoOpenSuppressedRef.current = true;
|
||||
setGeneratedCustomWorldProfile(null);
|
||||
setCustomWorldError(null);
|
||||
setCustomWorldAutoSaveError(null);
|
||||
setCustomWorldAutoSaveState('idle');
|
||||
setCustomWorldGenerationViewSource(null);
|
||||
setCustomWorldResultViewSource(null);
|
||||
setPlatformTab('create');
|
||||
setSelectionStage('platform');
|
||||
setSelectionStage('agent-workspace');
|
||||
};
|
||||
|
||||
const retryAgentDraftGeneration = () => {
|
||||
@@ -1000,25 +1083,79 @@ export function PreGameSelectionFlow({
|
||||
openCreationTypePicker();
|
||||
};
|
||||
|
||||
const openLibraryDetail = (
|
||||
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');
|
||||
};
|
||||
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) {
|
||||
// 阶段二要求草稿优先回到 Agent 工作区,而不是再次自动顶回结果页。
|
||||
isAgentDraftResultAutoOpenSuppressedRef.current = true;
|
||||
persistAgentUiState(work.sessionId, null);
|
||||
setGeneratedCustomWorldProfile(null);
|
||||
setCustomWorldError(null);
|
||||
setCustomWorldAutoSaveError(null);
|
||||
setCustomWorldAutoSaveState('idle');
|
||||
setCustomWorldGenerationViewSource(null);
|
||||
setCustomWorldResultViewSource(null);
|
||||
setPlatformTab('create');
|
||||
setSelectionStage('agent-workspace');
|
||||
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,
|
||||
setSelectionStage,
|
||||
],
|
||||
);
|
||||
|
||||
const openGalleryDetail = async (entry: CustomWorldGalleryCard) => {
|
||||
setSelectionStage('detail');
|
||||
@@ -1083,7 +1220,7 @@ export function PreGameSelectionFlow({
|
||||
}
|
||||
|
||||
const normalizedProfile = normalizeAgentBackedProfile(profile);
|
||||
const profileSignature = JSON.stringify(normalizedProfile);
|
||||
const profileSignature = stringifyAgentBackedProfile(normalizedProfile);
|
||||
const requestId = latestAutoSaveRequestIdRef.current + 1;
|
||||
latestAutoSaveRequestIdRef.current = requestId;
|
||||
setCustomWorldAutoSaveState('saving');
|
||||
@@ -1097,6 +1234,9 @@ export function PreGameSelectionFlow({
|
||||
|
||||
lastAutoSavedProfileSignatureRef.current = profileSignature;
|
||||
setSavedCustomWorldEntries(mutation.entries);
|
||||
if (authUi?.user) {
|
||||
void refreshCustomWorldWorks().catch(() => {});
|
||||
}
|
||||
setSelectedDetailEntry((current) => {
|
||||
if (!current || current.profileId === mutation.entry.profileId) {
|
||||
return mutation.entry;
|
||||
@@ -1119,7 +1259,99 @@ export function PreGameSelectionFlow({
|
||||
return null;
|
||||
}
|
||||
},
|
||||
[generatedCustomWorldProfile],
|
||||
[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(() => {
|
||||
@@ -1127,6 +1359,7 @@ export function PreGameSelectionFlow({
|
||||
setCustomWorldAutoSaveState('idle');
|
||||
setCustomWorldAutoSaveError(null);
|
||||
lastAutoSavedProfileSignatureRef.current = null;
|
||||
latestAgentResultSyncSignatureRef.current = null;
|
||||
if (customWorldAutoSaveTimeoutRef.current !== null) {
|
||||
window.clearTimeout(customWorldAutoSaveTimeoutRef.current);
|
||||
customWorldAutoSaveTimeoutRef.current = null;
|
||||
@@ -1138,7 +1371,11 @@ export function PreGameSelectionFlow({
|
||||
return;
|
||||
}
|
||||
|
||||
const nextSignature = JSON.stringify(generatedCustomWorldProfile);
|
||||
if (isCustomWorldAutoSaveBusyRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextSignature = stringifyAgentBackedProfile(generatedCustomWorldProfile);
|
||||
if (nextSignature === lastAutoSavedProfileSignatureRef.current) {
|
||||
return;
|
||||
}
|
||||
@@ -1150,7 +1387,28 @@ export function PreGameSelectionFlow({
|
||||
|
||||
const profileToSave = generatedCustomWorldProfile;
|
||||
customWorldAutoSaveTimeoutRef.current = window.setTimeout(() => {
|
||||
void saveGeneratedCustomWorld(profileToSave);
|
||||
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);
|
||||
|
||||
@@ -1160,7 +1418,13 @@ export function PreGameSelectionFlow({
|
||||
customWorldAutoSaveTimeoutRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [generatedCustomWorldProfile, saveGeneratedCustomWorld, selectionStage]);
|
||||
}, [
|
||||
generatedCustomWorldProfile,
|
||||
isAgentDraftResultView,
|
||||
saveGeneratedCustomWorld,
|
||||
selectionStage,
|
||||
syncAgentDraftResultProfile,
|
||||
]);
|
||||
|
||||
const openSavedCustomWorldEditor = (
|
||||
entry: CustomWorldLibraryEntry<CustomWorldProfile>,
|
||||
@@ -1200,6 +1464,7 @@ export function PreGameSelectionFlow({
|
||||
selectedDetailEntry.profileId,
|
||||
);
|
||||
setSavedCustomWorldEntries(mutation.entries);
|
||||
await refreshCustomWorldWorks().catch(() => []);
|
||||
setSelectedDetailEntry(mutation.entry);
|
||||
setPublishedGalleryEntries(await listCustomWorldGallery());
|
||||
} catch (error) {
|
||||
@@ -1221,6 +1486,7 @@ export function PreGameSelectionFlow({
|
||||
selectedDetailEntry.profileId,
|
||||
);
|
||||
setSavedCustomWorldEntries(mutation.entries);
|
||||
await refreshCustomWorldWorks().catch(() => []);
|
||||
setSelectedDetailEntry(mutation.entry);
|
||||
setPublishedGalleryEntries(await listCustomWorldGallery());
|
||||
} catch (error) {
|
||||
@@ -1249,6 +1515,7 @@ export function PreGameSelectionFlow({
|
||||
selectedDetailEntry.profileId,
|
||||
);
|
||||
setSavedCustomWorldEntries(entries);
|
||||
await refreshCustomWorldWorks().catch(() => []);
|
||||
setSelectedDetailEntry(null);
|
||||
setPlatformTab('create');
|
||||
setSelectionStage('platform');
|
||||
@@ -1269,6 +1536,10 @@ export function PreGameSelectionFlow({
|
||||
),
|
||||
);
|
||||
const resultViewError = customWorldAutoSaveError ?? customWorldError;
|
||||
const creationHubItems =
|
||||
customWorldWorkEntries.length > 0
|
||||
? customWorldWorkEntries
|
||||
: buildCreationHubFallbackItems(savedCustomWorldEntries);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -1281,47 +1552,106 @@ export function PreGameSelectionFlow({
|
||||
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}
|
||||
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();
|
||||
{platformTab === 'create' ? (
|
||||
<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}
|
||||
onResumeDraft={(sessionId) => {
|
||||
runProtectedAction(() => {
|
||||
void handleOpenCreationWork({
|
||||
workId: `draft:${sessionId}`,
|
||||
sourceType: 'agent_session',
|
||||
status: 'draft',
|
||||
title: '',
|
||||
subtitle: '',
|
||||
summary: '',
|
||||
coverImageSrc: null,
|
||||
coverRenderMode: 'image',
|
||||
coverCharacterImageSrcs: [],
|
||||
updatedAt: new Date().toISOString(),
|
||||
publishedAt: null,
|
||||
stage: null,
|
||||
stageLabel: '',
|
||||
playableNpcCount: 0,
|
||||
landmarkCount: 0,
|
||||
roleVisualReadyCount: 0,
|
||||
roleAnimationReadyCount: 0,
|
||||
roleAssetSummaryLabel: null,
|
||||
sessionId,
|
||||
profileId: null,
|
||||
canResume: true,
|
||||
canEnterWorld: false,
|
||||
});
|
||||
});
|
||||
}}
|
||||
onEnterPublished={(profileId) => {
|
||||
runProtectedAction(() => {
|
||||
const matchedWork = creationHubItems.find(
|
||||
(entry) => entry.profileId === profileId,
|
||||
);
|
||||
if (!matchedWork) {
|
||||
return;
|
||||
}
|
||||
void handleOpenCreationWork(matchedWork);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<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}
|
||||
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>
|
||||
)}
|
||||
|
||||
@@ -1501,7 +1831,28 @@ export function PreGameSelectionFlow({
|
||||
}}
|
||||
onBack={
|
||||
isAgentDraftResultView
|
||||
? leaveAgentDraftResult
|
||||
? () => {
|
||||
void (async () => {
|
||||
const currentProfile =
|
||||
generatedCustomWorldProfile ??
|
||||
buildCustomWorldProfileFromAgentDraft(
|
||||
agentSession,
|
||||
);
|
||||
|
||||
if (currentProfile && activeAgentSessionId) {
|
||||
await syncAgentDraftResultProfile(currentProfile);
|
||||
}
|
||||
|
||||
leaveAgentDraftResult();
|
||||
})().catch((error) => {
|
||||
setCustomWorldError(
|
||||
resolveErrorMessage(
|
||||
error,
|
||||
'返回创作前同步草稿失败。',
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
: leaveCustomWorldResult
|
||||
}
|
||||
onEditSetting={undefined}
|
||||
@@ -1509,10 +1860,40 @@ export function PreGameSelectionFlow({
|
||||
onContinueExpand={undefined}
|
||||
onEnterWorld={() => {
|
||||
runProtectedAction(() => {
|
||||
handleCustomWorldSelect(generatedCustomWorldProfile);
|
||||
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}
|
||||
readOnly={isAgentDraftResultEditingFrozen}
|
||||
compactAgentResultMode={isAgentDraftResultView}
|
||||
backLabel={isAgentDraftResultView ? '返回创作' : undefined}
|
||||
editActionLabel="去Agent调整设定"
|
||||
enterWorldActionLabel="进入世界"
|
||||
|
||||
Reference in New Issue
Block a user