refactor: 收口剩余草稿打开 intent

This commit is contained in:
2026-06-04 06:12:26 +08:00
parent e570d50e9f
commit c93b8fb570
6 changed files with 422 additions and 41 deletions

View File

@@ -447,8 +447,11 @@ import {
type PendingDraftShelfKind,
type PendingDraftShelfMap,
type PendingDraftShelfMetadata,
resolveBigFishDraftOpenIntent,
resolveMatch3DDraftOpenIntent,
resolvePuzzleDraftOpenIntent,
resolveSquareHoleDraftOpenIntent,
resolveVisualNovelDraftOpenIntent,
} from './platformDraftGenerationShelfModel';
import {
canExposePublicWork,
@@ -10685,42 +10688,42 @@ export function PlatformEntryFlowShellImpl({
item: SquareHoleWorkSummary,
options: { forceDraft?: boolean } = {},
) => {
const openIntent = resolveSquareHoleDraftOpenIntent({
item,
forceDraft: options.forceDraft,
activeSessionId: squareHoleSession?.sessionId,
hasActiveGenerationRunning: isMiniGameDraftGenerating(
squareHoleGenerationState,
),
isGenerationReady: isMiniGameDraftReady(squareHoleGenerationState),
});
setSquareHoleRun(null);
setSquareHoleError(null);
setSquareHoleProfile(null);
markDraftNoticeSeen(
collectDraftNoticeKeys('square-hole', [
item.workId,
item.profileId,
item.sourceSessionId,
]),
);
markDraftNoticeSeen(openIntent.noticeKeys);
if (item.publicationStatus === 'published' && !options.forceDraft) {
if (openIntent.type === 'open-published-detail') {
openPublicWorkDetail(mapSquareHoleWorkToPublicWorkDetail(item));
return;
}
if (!item.sourceSessionId?.trim()) {
setSquareHoleError('这份方洞挑战草稿缺少会话信息,请重新开始创作。');
if (openIntent.type === 'missing-session') {
setSquareHoleError(openIntent.errorMessage);
return;
}
if (
item.sourceSessionId === squareHoleSession?.sessionId &&
isMiniGameDraftGenerating(squareHoleGenerationState)
) {
if (openIntent.type === 'active-generation') {
enterCreateTab();
selectionStageRef.current = 'square-hole-generating';
setSelectionStage('square-hole-generating');
return;
}
if (!isMiniGameDraftReady(squareHoleGenerationState)) {
if (openIntent.shouldClearGenerationState) {
setSquareHoleGenerationState(null);
}
const restoredSession = await squareHoleFlow.restoreDraft(
item.sourceSessionId,
openIntent.sourceSessionId,
);
if (!restoredSession) {
await refreshSquareHoleShelf().catch(() => undefined);
@@ -10756,21 +10759,23 @@ export function PlatformEntryFlowShellImpl({
const openBigFishDraft = useCallback(
async (item: BigFishWorkSummary) => {
const openIntent = resolveBigFishDraftOpenIntent({
item,
activeSessionId: bigFishSession?.sessionId,
hasActiveGenerationRunning: isMiniGameDraftGenerating(
bigFishGenerationState,
),
});
setBigFishRun(null);
markDraftNoticeSeen(
collectDraftNoticeKeys('big-fish', [item.workId, item.sourceSessionId]),
);
if (
item.sourceSessionId === bigFishSession?.sessionId &&
isMiniGameDraftGenerating(bigFishGenerationState)
) {
markDraftNoticeSeen(openIntent.noticeKeys);
if (openIntent.type === 'active-generation') {
enterCreateTab();
selectionStageRef.current = 'big-fish-generating';
setSelectionStage('big-fish-generating');
return;
}
const restoredSession = await bigFishFlow.restoreDraft(
item.sourceSessionId,
openIntent.sourceSessionId,
);
if (!restoredSession) {
await refreshBigFishShelf().catch(() => undefined);
@@ -10823,27 +10828,27 @@ export function PlatformEntryFlowShellImpl({
item: VisualNovelWorkSummary,
options: { forceDraft?: boolean } = {},
) => {
if (item.publishStatus === 'published' && !options.forceDraft) {
const openIntent = resolveVisualNovelDraftOpenIntent({
item,
forceDraft: options.forceDraft,
activeSessionId: visualNovelSession?.sessionId,
hasActiveGenerationRunning: visualNovelGenerationPhase === 'generating',
hasActiveSessionDraft: Boolean(visualNovelSession?.draft),
});
if (openIntent.type === 'open-published-detail') {
openPublicWorkDetail(mapVisualNovelWorkToPublicWorkDetail(item));
return;
}
markDraftNoticeSeen(
collectDraftNoticeKeys('visual-novel', [item.profileId]),
);
if (
item.profileId === visualNovelSession?.sessionId &&
visualNovelGenerationPhase === 'generating'
) {
markDraftNoticeSeen(openIntent.noticeKeys);
if (openIntent.type === 'active-generation') {
enterCreateTab();
selectionStageRef.current = 'visual-novel-generating';
setSelectionStage('visual-novel-generating');
return;
}
if (
item.profileId === visualNovelSession?.sessionId &&
visualNovelSession.draft
) {
if (openIntent.type === 'current-result') {
enterCreateTab();
setSelectionStage('visual-novel-result');
return;
@@ -10856,7 +10861,7 @@ export function PlatformEntryFlowShellImpl({
setIsVisualNovelBusy(true);
try {
const { work } = await getVisualNovelWorkDetail(item.profileId);
const { work } = await getVisualNovelWorkDetail(openIntent.profileId);
setVisualNovelWork(work);
setVisualNovelSession(buildVisualNovelSessionFromWorkDetail(work));
enterCreateTab();

View File

@@ -3,6 +3,8 @@ import { describe, expect, test } from 'vitest';
import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/bigFishWorkSummary';
import type { Match3DWorkSummary } from '../../../packages/shared/src/contracts/match3dWorks';
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
import type { SquareHoleWorkSummary } from '../../../packages/shared/src/contracts/squareHoleWorks';
import type { VisualNovelWorkSummary } from '../../../packages/shared/src/contracts/visualNovel';
import { buildCreationWorkShelfItems } from '../custom-world-home/creationWorkShelf';
import {
buildCreationWorkShelfRuntimeState,
@@ -16,8 +18,11 @@ import {
hasUnreadDraftGenerationUpdates,
mergeBigFishWorkSummary,
mergePuzzleWorkSummary,
resolveBigFishDraftOpenIntent,
resolveMatch3DDraftOpenIntent,
resolvePuzzleDraftOpenIntent,
resolveSquareHoleDraftOpenIntent,
resolveVisualNovelDraftOpenIntent,
} from './platformDraftGenerationShelfModel';
describe('platformDraftGenerationShelfModel', () => {
@@ -150,6 +155,131 @@ describe('platformDraftGenerationShelfModel', () => {
});
});
test('resolveBigFishDraftOpenIntent reopens active generating session before restoring draft', () => {
expect(
resolveBigFishDraftOpenIntent({
item: buildBigFishWork(),
activeSessionId: 'big-fish-session-base',
hasActiveGenerationRunning: true,
}),
).toMatchObject({
type: 'active-generation',
sourceSessionId: 'big-fish-session-base',
});
expect(
resolveBigFishDraftOpenIntent({
item: buildBigFishWork(),
activeSessionId: 'other-session',
hasActiveGenerationRunning: true,
}),
).toMatchObject({
type: 'restore-draft',
sourceSessionId: 'big-fish-session-base',
});
});
test('resolveSquareHoleDraftOpenIntent handles published, missing, active and restore states', () => {
expect(
resolveSquareHoleDraftOpenIntent({
item: buildSquareHoleWork({ publicationStatus: 'published' }),
activeSessionId: null,
hasActiveGenerationRunning: false,
isGenerationReady: false,
}),
).toMatchObject({
type: 'open-published-detail',
});
expect(
resolveSquareHoleDraftOpenIntent({
item: buildSquareHoleWork({ sourceSessionId: null }),
forceDraft: true,
activeSessionId: null,
hasActiveGenerationRunning: false,
isGenerationReady: false,
}),
).toMatchObject({
type: 'missing-session',
});
expect(
resolveSquareHoleDraftOpenIntent({
item: buildSquareHoleWork(),
activeSessionId: 'square-hole-session-base',
hasActiveGenerationRunning: true,
isGenerationReady: false,
}),
).toMatchObject({
type: 'active-generation',
sourceSessionId: 'square-hole-session-base',
});
expect(
resolveSquareHoleDraftOpenIntent({
item: buildSquareHoleWork(),
activeSessionId: 'other-session',
hasActiveGenerationRunning: false,
isGenerationReady: false,
}),
).toMatchObject({
type: 'restore-draft',
shouldClearGenerationState: true,
});
});
test('resolveVisualNovelDraftOpenIntent handles published, active, current result and load detail states', () => {
expect(
resolveVisualNovelDraftOpenIntent({
item: buildVisualNovelWork({ publishStatus: 'published' }),
activeSessionId: null,
hasActiveGenerationRunning: false,
hasActiveSessionDraft: false,
}),
).toMatchObject({
type: 'open-published-detail',
});
expect(
resolveVisualNovelDraftOpenIntent({
item: buildVisualNovelWork(),
forceDraft: true,
activeSessionId: 'visual-novel-profile-base',
hasActiveGenerationRunning: true,
hasActiveSessionDraft: false,
}),
).toMatchObject({
type: 'active-generation',
profileId: 'visual-novel-profile-base',
});
expect(
resolveVisualNovelDraftOpenIntent({
item: buildVisualNovelWork(),
forceDraft: true,
activeSessionId: 'visual-novel-profile-base',
hasActiveGenerationRunning: false,
hasActiveSessionDraft: true,
}),
).toMatchObject({
type: 'current-result',
profileId: 'visual-novel-profile-base',
});
expect(
resolveVisualNovelDraftOpenIntent({
item: buildVisualNovelWork(),
forceDraft: true,
activeSessionId: 'other-profile',
hasActiveGenerationRunning: false,
hasActiveSessionDraft: false,
}),
).toMatchObject({
type: 'load-detail',
profileId: 'visual-novel-profile-base',
});
});
test('buildPendingPuzzleWorks creates failed puzzle placeholder with stable ids and fallback title', () => {
const pending = buildPendingPuzzleWorks(
{
@@ -425,3 +555,52 @@ function buildBigFishWork(
...overrides,
};
}
function buildSquareHoleWork(
overrides: Partial<SquareHoleWorkSummary> = {},
): SquareHoleWorkSummary {
return {
workId: 'square-hole-work-base',
profileId: 'square-hole-profile-base',
ownerUserId: 'user-1',
sourceSessionId: 'square-hole-session-base',
gameName: '潮雾方洞',
themeText: '潮雾港口',
twistRule: '避开雾门',
summary: '潮雾港口方洞挑战。',
tags: [],
coverImageSrc: null,
backgroundPrompt: '潮雾港口',
backgroundImageSrc: null,
shapeOptions: [],
holeOptions: [],
shapeCount: 1,
difficulty: 1,
publicationStatus: 'draft',
playCount: 0,
updatedAt: '2026-06-03T08:00:00.000Z',
publishedAt: null,
publishReady: false,
...overrides,
};
}
function buildVisualNovelWork(
overrides: Partial<VisualNovelWorkSummary> = {},
): VisualNovelWorkSummary {
return {
runtimeKind: 'visual-novel',
profileId: 'visual-novel-profile-base',
ownerUserId: 'user-1',
title: '潮雾视觉小说',
description: '潮雾港口视觉小说。',
coverImageSrc: null,
tags: [],
publishStatus: 'draft',
publishReady: false,
playCount: 0,
updatedAt: '2026-06-03T08:00:00.000Z',
publishedAt: null,
...overrides,
};
}

View File

@@ -148,6 +148,61 @@ export type Match3DDraftOpenIntent =
noticeKeys: string[];
};
export type BigFishDraftOpenIntent =
| {
type: 'active-generation';
noticeKeys: string[];
sourceSessionId: string;
}
| {
type: 'restore-draft';
noticeKeys: string[];
sourceSessionId: string;
};
export type SquareHoleDraftOpenIntent =
| {
type: 'open-published-detail';
noticeKeys: string[];
}
| {
type: 'missing-session';
noticeKeys: string[];
errorMessage: string;
}
| {
type: 'active-generation';
noticeKeys: string[];
sourceSessionId: string;
}
| {
type: 'restore-draft';
noticeKeys: string[];
sourceSessionId: string;
shouldClearGenerationState: boolean;
};
export type VisualNovelDraftOpenIntent =
| {
type: 'open-published-detail';
noticeKeys: string[];
}
| {
type: 'active-generation';
noticeKeys: string[];
profileId: string;
}
| {
type: 'current-result';
noticeKeys: string[];
profileId: string;
}
| {
type: 'load-detail';
noticeKeys: string[];
profileId: string;
};
export function buildDraftNoticeKey(
kind: CreationWorkShelfKind,
id: string,
@@ -433,6 +488,29 @@ export function buildMatch3DDraftOpenNoticeKeys(item: Match3DWorkSummary) {
]);
}
export function buildBigFishDraftOpenNoticeKeys(item: BigFishWorkSummary) {
return collectDraftNoticeKeys('big-fish', [
item.workId,
item.sourceSessionId,
]);
}
export function buildSquareHoleDraftOpenNoticeKeys(
item: SquareHoleWorkSummary,
) {
return collectDraftNoticeKeys('square-hole', [
item.workId,
item.profileId,
item.sourceSessionId,
]);
}
export function buildVisualNovelDraftOpenNoticeKeys(
item: VisualNovelWorkSummary,
) {
return collectDraftNoticeKeys('visual-novel', [item.profileId]);
}
export function resolvePuzzleDraftOpenIntent(params: {
item: PuzzleWorkSummary;
notices: DraftGenerationNoticeMap;
@@ -628,6 +706,117 @@ export function resolveMatch3DDraftOpenIntent(params: {
return { type: 'restore-draft', noticeKeys };
}
export function resolveBigFishDraftOpenIntent(params: {
item: BigFishWorkSummary;
activeSessionId?: string | null;
hasActiveGenerationRunning: boolean;
}): BigFishDraftOpenIntent {
const { item, activeSessionId, hasActiveGenerationRunning } = params;
const noticeKeys = buildBigFishDraftOpenNoticeKeys(item);
if (item.sourceSessionId === activeSessionId && hasActiveGenerationRunning) {
return {
type: 'active-generation',
noticeKeys,
sourceSessionId: item.sourceSessionId,
};
}
return {
type: 'restore-draft',
noticeKeys,
sourceSessionId: item.sourceSessionId,
};
}
export function resolveSquareHoleDraftOpenIntent(params: {
item: SquareHoleWorkSummary;
forceDraft?: boolean;
activeSessionId?: string | null;
hasActiveGenerationRunning: boolean;
isGenerationReady: boolean;
}): SquareHoleDraftOpenIntent {
const {
item,
forceDraft = false,
activeSessionId,
hasActiveGenerationRunning,
isGenerationReady,
} = params;
const noticeKeys = buildSquareHoleDraftOpenNoticeKeys(item);
if (item.publicationStatus === 'published' && !forceDraft) {
return { type: 'open-published-detail', noticeKeys };
}
const sourceSessionId = normalizeDraftNoticeId(item.sourceSessionId);
if (!sourceSessionId) {
return {
type: 'missing-session',
noticeKeys,
errorMessage: '这份方洞挑战草稿缺少会话信息,请重新开始创作。',
};
}
if (sourceSessionId === activeSessionId && hasActiveGenerationRunning) {
return {
type: 'active-generation',
noticeKeys,
sourceSessionId,
};
}
return {
type: 'restore-draft',
noticeKeys,
sourceSessionId,
shouldClearGenerationState: !isGenerationReady,
};
}
export function resolveVisualNovelDraftOpenIntent(params: {
item: VisualNovelWorkSummary;
forceDraft?: boolean;
activeSessionId?: string | null;
hasActiveGenerationRunning: boolean;
hasActiveSessionDraft: boolean;
}): VisualNovelDraftOpenIntent {
const {
item,
forceDraft = false,
activeSessionId,
hasActiveGenerationRunning,
hasActiveSessionDraft,
} = params;
const noticeKeys = buildVisualNovelDraftOpenNoticeKeys(item);
if (item.publishStatus === 'published' && !forceDraft) {
return { type: 'open-published-detail', noticeKeys };
}
const isCurrentSession = item.profileId === activeSessionId;
if (isCurrentSession && hasActiveGenerationRunning) {
return {
type: 'active-generation',
noticeKeys,
profileId: item.profileId,
};
}
if (isCurrentSession && hasActiveSessionDraft) {
return {
type: 'current-result',
noticeKeys,
profileId: item.profileId,
};
}
return {
type: 'load-detail',
noticeKeys,
profileId: item.profileId,
};
}
export function buildCreationWorkShelfRuntimeState(params: {
item: CreationWorkShelfItem;
notices: DraftGenerationNoticeMap;