refactor: 收口剩余草稿打开 intent
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user