Extend square-hole creation flow with visual asset timeout guard

This commit is contained in:
kdletters
2026-05-05 15:27:09 +08:00
parent 2252afb080
commit 60b667a9d1
30 changed files with 2838 additions and 215 deletions

View File

@@ -32,22 +32,6 @@ import type {
Match3DWorkProfile,
Match3DWorkSummary,
} from '../../../packages/shared/src/contracts/match3dWorks';
import type {
CreateSquareHoleSessionRequest,
ExecuteSquareHoleActionRequest,
SendSquareHoleMessageRequest,
SquareHoleActionResponse,
SquareHoleSessionResponse,
SquareHoleSessionSnapshot,
} from '../../../packages/shared/src/contracts/squareHoleAgent';
import type {
DropSquareHoleShapeRequest,
SquareHoleRunSnapshot,
} from '../../../packages/shared/src/contracts/squareHoleRuntime';
import type {
SquareHoleWorkProfile,
SquareHoleWorkSummary,
} from '../../../packages/shared/src/contracts/squareHoleWorks';
import type {
PuzzleAgentActionRequest,
PuzzleAgentOperationRecord,
@@ -72,6 +56,21 @@ import type {
ProfileSaveArchiveResumeResponse,
ProfileSaveArchiveSummary,
} from '../../../packages/shared/src/contracts/runtime';
import type {
CreateSquareHoleSessionRequest,
ExecuteSquareHoleActionRequest,
SendSquareHoleMessageRequest,
SquareHoleActionResponse,
SquareHoleSessionResponse,
SquareHoleSessionSnapshot,
} from '../../../packages/shared/src/contracts/squareHoleAgent';
import type {
SquareHoleRunSnapshot,
} from '../../../packages/shared/src/contracts/squareHoleRuntime';
import type {
SquareHoleWorkProfile,
SquareHoleWorkSummary,
} from '../../../packages/shared/src/contracts/squareHoleWorks';
import { buildCustomWorldPlayableCharacters } from '../../data/characterPresets';
import {
buildPublicWorkStagePath,
@@ -123,6 +122,7 @@ import {
buildBigFishGenerationAnchorEntries,
buildMiniGameDraftGenerationProgress,
buildPuzzleGenerationAnchorEntries,
buildSquareHoleGenerationAnchorEntries,
createMiniGameDraftGenerationState,
type MiniGameDraftGenerationState,
} from '../../services/miniGameDraftGenerationProgress';
@@ -552,8 +552,12 @@ function mapPublicWorkDetailToSquareHoleWork(
summary: entry.summaryText,
tags: entry.themeTags,
coverImageSrc: entry.coverImageSrc,
shapeCount: 8,
difficulty: 4,
backgroundPrompt: entry.backgroundPrompt ?? '方洞挑战运行背景',
backgroundImageSrc: entry.backgroundImageSrc ?? null,
shapeOptions: entry.shapeOptions ?? [],
holeOptions: entry.holeOptions ?? [],
shapeCount: entry.shapeCount ?? 8,
difficulty: entry.difficulty ?? 4,
publicationStatus: 'published',
playCount: entry.playCount ?? 0,
updatedAt: entry.updatedAt,
@@ -581,7 +585,11 @@ function buildSquareHoleProfileFromSession(
twistRule: draft.twistRule,
summary: draft.summary,
tags: draft.tags,
coverImageSrc: null,
coverImageSrc: draft.coverImageSrc ?? null,
backgroundPrompt: draft.backgroundPrompt,
backgroundImageSrc: draft.backgroundImageSrc ?? null,
shapeOptions: draft.shapeOptions,
holeOptions: draft.holeOptions,
shapeCount: draft.shapeCount,
difficulty: draft.difficulty,
publicationStatus: 'draft',
@@ -608,13 +616,6 @@ function mergeBigFishWorkSummary(
: current;
}
function mergeSquareHoleWorkSummary(
current: SquareHoleWorkSummary,
updated: SquareHoleWorkSummary,
): SquareHoleWorkSummary {
return current.profileId === updated.profileId ? updated : current;
}
async function resolvePublicWorkAuthorSummary(
entry: PlatformPublicGalleryCard,
): Promise<PublicUserSummary | null> {
@@ -1086,6 +1087,8 @@ export function PlatformEntryFlowShellImpl({
useState<SquareHoleRuntimeReturnStage>('square-hole-result');
const [isSquareHoleLoadingLibrary, setIsSquareHoleLoadingLibrary] =
useState(false);
const [squareHoleGenerationState, setSquareHoleGenerationState] =
useState<MiniGameDraftGenerationState | null>(null);
const [bigFishRun, setBigFishRun] =
useState<BigFishRuntimeSnapshotResponse | null>(null);
const [bigFishRuntimeShare, setBigFishRuntimeShare] = useState<{
@@ -1817,7 +1820,7 @@ export function PlatformEntryFlowShellImpl({
workspaceStage: 'square-hole-agent-workspace',
resultStage: 'square-hole-result',
platformStage: 'platform',
isCompileAction: (payload) => payload.action === 'square_hole_compile_draft',
isCompileAction: () => false,
resolveErrorMessage: resolveSquareHoleErrorMessage,
errorMessages: {
open: '开启方洞挑战共创工作台失败。',
@@ -1831,9 +1834,30 @@ export function PlatformEntryFlowShellImpl({
onSessionOpened: () => {
setShowCreationTypeModal(false);
},
beforeExecuteAction: ({ payload }) => {
if (payload.action === 'square_hole_compile_draft') {
setSquareHoleGenerationState(
createMiniGameDraftGenerationState('square-hole'),
);
setSelectionStage('square-hole-generating');
}
if (payload.action === 'square_hole_generate_visual_assets') {
setSquareHoleGenerationState((current) => ({
...(current ?? createMiniGameDraftGenerationState('square-hole')),
phase: 'square-hole-cover',
completedAssetCount: 0,
totalAssetCount: 0,
error: null,
}));
setSelectionStage('square-hole-generating');
}
},
onActionComplete: async ({ payload, response, setSession }) => {
setSession(response.session);
if (payload.action !== 'square_hole_compile_draft') {
if (
payload.action !== 'square_hole_compile_draft' &&
payload.action !== 'square_hole_generate_visual_assets'
) {
return;
}
@@ -1843,12 +1867,79 @@ export function PlatformEntryFlowShellImpl({
return;
}
if (payload.action === 'square_hole_compile_draft') {
try {
const assetResponse = await squareHoleCreationClient.executeAction(
response.session.sessionId,
{
action: 'square_hole_generate_visual_assets',
},
);
setSession(assetResponse.session);
const assetProfileId = assetResponse.session.draft?.profileId;
if (!assetProfileId) {
setSquareHoleProfile(
buildSquareHoleProfileFromSession(assetResponse.session),
);
setSelectionStage('square-hole-result');
return;
}
const { item } = await getSquareHoleWorkDetail(assetProfileId);
setSquareHoleProfile(item);
setSquareHoleGenerationState((current) => ({
...(current ?? createMiniGameDraftGenerationState('square-hole')),
phase: 'ready',
completedAssetCount: item.shapeOptions.length + 2,
totalAssetCount: item.shapeOptions.length + 2,
error: null,
}));
await refreshSquareHoleShelf().catch(() => undefined);
setSelectionStage('square-hole-result');
} catch (error) {
const errorMessage = resolveSquareHoleErrorMessage(
error,
'生成方洞挑战图片失败。',
);
setSquareHoleError(errorMessage);
setSquareHoleGenerationState((current) => ({
...(current ?? createMiniGameDraftGenerationState('square-hole')),
phase: 'failed',
error: errorMessage,
}));
setSquareHoleProfile(buildSquareHoleProfileFromSession(response.session));
setSelectionStage('square-hole-generating');
}
return;
}
try {
const { item } = await getSquareHoleWorkDetail(profileId);
setSquareHoleProfile(item);
setSquareHoleGenerationState((current) => ({
...(current ?? createMiniGameDraftGenerationState('square-hole')),
phase: 'ready',
completedAssetCount: item.shapeOptions.length + 2,
totalAssetCount: item.shapeOptions.length + 2,
error: null,
}));
await refreshSquareHoleShelf().catch(() => undefined);
setSelectionStage('square-hole-result');
} catch {
setSquareHoleProfile(buildSquareHoleProfileFromSession(response.session));
setSelectionStage('square-hole-result');
}
},
onActionError: ({ payload, errorMessage }) => {
if (
payload.action === 'square_hole_compile_draft' ||
payload.action === 'square_hole_generate_visual_assets'
) {
setSquareHoleGenerationState((current) => ({
...(current ?? createMiniGameDraftGenerationState('square-hole')),
phase: 'failed',
error: errorMessage,
}));
setSelectionStage('square-hole-generating');
}
},
});
@@ -2047,6 +2138,7 @@ export function PlatformEntryFlowShellImpl({
setSquareHoleProfile(null);
setSquareHoleRun(null);
setSquareHoleError(null);
setSquareHoleGenerationState(null);
setSquareHoleRuntimeReturnStage('square-hole-result');
setStreamingSquareHoleReplyText('');
setIsStreamingSquareHoleReply(false);
@@ -2162,6 +2254,7 @@ export function PlatformEntryFlowShellImpl({
setSquareHoleGalleryEntries([]);
setSquareHoleRun(null);
setSquareHoleRuntimeReturnStage('square-hole-result');
setSquareHoleGenerationState(null);
setSquareHoleError(null);
setStreamingSquareHoleReplyText('');
setIsStreamingSquareHoleReply(false);
@@ -2291,6 +2384,7 @@ export function PlatformEntryFlowShellImpl({
const leaveSquareHoleFlow = useCallback(() => {
setSquareHoleRun(null);
setSquareHoleRuntimeReturnStage('square-hole-result');
setSquareHoleGenerationState(null);
squareHoleFlow.leaveFlow();
}, [squareHoleFlow]);
@@ -2316,6 +2410,20 @@ export function PlatformEntryFlowShellImpl({
const executeSquareHoleAction = squareHoleFlow.executeAction;
const retrySquareHoleAssetGeneration = useCallback(() => {
const session = squareHoleSession;
if (!session?.draft?.profileId) {
void executeSquareHoleAction({
action: 'square_hole_compile_draft',
});
return;
}
void executeSquareHoleAction({
action: 'square_hole_generate_visual_assets',
});
}, [executeSquareHoleAction, squareHoleSession]);
const executePuzzleAction = puzzleFlow.executeAction;
const retryPuzzleDraftGeneration = useCallback(() => {
@@ -4013,6 +4121,7 @@ export function PlatformEntryFlowShellImpl({
return;
}
setSquareHoleGenerationState(null);
const restoredSession = await squareHoleFlow.restoreDraft(
item.sourceSessionId,
);
@@ -5583,6 +5692,50 @@ export function PlatformEntryFlowShellImpl({
</motion.div>
)}
{selectionStage === 'square-hole-generating' && (
<motion.div
key="square-hole-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={
squareHoleSession?.lastAssistantReply ??
'正在整理当前方洞挑战草稿。'
}
anchorEntries={buildSquareHoleGenerationAnchorEntries(
squareHoleSession,
)}
progress={buildMiniGameDraftGenerationProgress(
squareHoleGenerationState,
)}
isGenerating={isSquareHoleBusy}
error={squareHoleError}
onBack={leaveSquareHoleFlow}
onEditSetting={() => {
setSelectionStage('square-hole-agent-workspace');
}}
onRetry={retrySquareHoleAssetGeneration}
onInterrupt={undefined}
backLabel="返回创作中心"
settingActionLabel={null}
retryLabel="重新生成图片"
settingTitle="当前方洞挑战"
settingDescription={null}
progressTitle="方洞挑战图片生成进度"
activeBadgeLabel="图片生成中"
pausedBadgeLabel="图片生成已暂停"
idleBadgeLabel="等待返回结果页"
/>
</Suspense>
</motion.div>
)}
{selectionStage === 'square-hole-result' && squareHoleSession?.draft && (
<motion.div
key="square-hole-result"