refactor: 收口视觉小说详情 session 映射
This commit is contained in:
@@ -1405,9 +1405,9 @@
|
||||
|
||||
## 2026-06-04 Platform Mini Game Session Mapping Model 收口
|
||||
|
||||
- 背景:`PlatformEntryFlowShellImpl.tsx` 顶部仍保留拼图 runtime 恢复、方洞 session draft 转 profile、跳一跳 pending session、敲木鱼 detail 恢复和敲木鱼 pending session 等纯 DTO 映射,壳层需要理解 sessionId 优先级、拼图稳定 ID、方洞草稿 profile 默认值和 pending draft 默认值。
|
||||
- 决策:新增 `src/components/platform-entry/platformMiniGameSessionMappingModel.ts`,收口 `buildPuzzleRuntimeWorkFromSession`、`buildSquareHoleProfileFromSession`、`buildJumpHopPendingSession`、`buildWoodenFishSessionFromWorkDetail` 与 `buildWoodenFishPendingSession`。Module 复用 `normalizeCreationUrlValue` 与 `platformPuzzleIdentityModel`;壳层只保留网络读取、React state、URL 写入和 stage 切换副作用。
|
||||
- 影响范围:拼图 runtime URL 恢复、方洞挑战草稿 profile 构造、跳一跳生成中作品架打开、敲木鱼生成中作品架打开和敲木鱼草稿 detail 恢复。
|
||||
- 背景:`PlatformEntryFlowShellImpl.tsx` 顶部仍保留拼图 runtime 恢复、方洞 session draft 转 profile、视觉小说 work detail 转 Agent session、跳一跳 pending session、敲木鱼 detail 恢复和敲木鱼 pending session 等纯 DTO 映射,壳层需要理解 sessionId 优先级、拼图稳定 ID、方洞草稿 profile 默认值、视觉小说 work/session fallback 和 pending draft 默认值。
|
||||
- 决策:新增 `src/components/platform-entry/platformMiniGameSessionMappingModel.ts`,收口 `buildPuzzleRuntimeWorkFromSession`、`buildSquareHoleProfileFromSession`、`buildVisualNovelSessionFromWorkDetail`、`buildJumpHopPendingSession`、`buildWoodenFishSessionFromWorkDetail` 与 `buildWoodenFishPendingSession`。Module 复用 `normalizeCreationUrlValue` 与 `platformPuzzleIdentityModel`;壳层只保留网络读取、React state、URL 写入和 stage 切换副作用。
|
||||
- 影响范围:拼图 runtime URL 恢复、方洞挑战草稿 profile 构造、视觉小说草稿作品架恢复、跳一跳生成中作品架打开、敲木鱼生成中作品架打开和敲木鱼草稿 detail 恢复。
|
||||
- 验证方式:`npm run test -- src/components/platform-entry/platformMiniGameSessionMappingModel.test.ts`、针对新 Module 和 `PlatformEntryFlowShellImpl.tsx` 执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
|
||||
- 关联文档:`docs/technical/【前端架构】PlatformMiniGameSessionMappingModel收口计划-2026-06-04.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ AI 文字游戏模板接入以 [AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_
|
||||
|
||||
平台入口生成页进度 tick 的 stage 到生成状态映射、终态判定和视觉小说轻量生成特例收口到 `src/components/platform-entry/platformGenerationProgressTickModel.ts`,壳层只保留 `Date.now()`、`setInterval` 和 cleanup 副作用,规则见 [【前端架构】PlatformGenerationProgressTickModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformGenerationProgressTickModel收口计划-2026-06-04.md)。
|
||||
|
||||
平台壳的拼图 runtime 恢复 work、方洞 session draft 转 profile、跳一跳 pending session、敲木鱼 detail 恢复 session 和敲木鱼 pending session DTO 映射收口到 `src/components/platform-entry/platformMiniGameSessionMappingModel.ts`,壳层只保留网络、状态、URL 与 stage 副作用,规则见 [【前端架构】PlatformMiniGameSessionMappingModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformMiniGameSessionMappingModel收口计划-2026-06-04.md)。
|
||||
平台壳的拼图 runtime 恢复 work、方洞 session draft 转 profile、视觉小说 work detail 转 Agent session、跳一跳 pending session、敲木鱼 detail 恢复 session 和敲木鱼 pending session DTO 映射收口到 `src/components/platform-entry/platformMiniGameSessionMappingModel.ts`,壳层只保留网络、状态、URL 与 stage 副作用,规则见 [【前端架构】PlatformMiniGameSessionMappingModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformMiniGameSessionMappingModel收口计划-2026-06-04.md)。
|
||||
|
||||
平台小游戏生成状态的恢复、失败 / 完成收尾、展示 rebase、拼图后端进度合并、抓大鹅生成资产旁路进度合并和 ready / generating 判定收口到 `src/components/platform-entry/platformMiniGameDraftGenerationStateModel.ts`,壳层只保留 API、后台任务、React state、URL 与 stage 副作用,规则见 [【前端架构】PlatformMiniGameDraftGenerationStateModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformMiniGameDraftGenerationStateModel收口计划-2026-06-04.md)。
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## 背景
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 顶部曾保留拼图 runtime 恢复、方洞挑战 session draft 转 profile、跳一跳 pending session、敲木鱼 work detail 恢复和敲木鱼 pending session 多段纯 DTO 映射。它们没有 React state、网络请求、路由、弹窗或计时副作用,却住在大型平台壳内;新增或修正生成中草稿恢复时,需要在壳层里理解 sessionId 优先级、拼图稳定 ID、方洞 profile 默认值、pending draft 默认值和木鱼 fallback 规则。
|
||||
`PlatformEntryFlowShellImpl.tsx` 顶部曾保留拼图 runtime 恢复、方洞挑战 session draft 转 profile、视觉小说 work detail 转 Agent session、跳一跳 pending session、敲木鱼 work detail 恢复和敲木鱼 pending session 多段纯 DTO 映射。它们没有 React state、网络请求、路由、弹窗或计时副作用,却住在大型平台壳内;新增或修正生成中草稿恢复时,需要在壳层里理解 sessionId 优先级、拼图稳定 ID、方洞 profile 默认值、视觉小说 work/session fallback、pending draft 默认值和木鱼 fallback 规则。
|
||||
|
||||
这些规则属于平台壳 session / work 恢复映射,应成为可测试的 **Module**。壳层只负责调用网络、写 React state、写 URL 和切换 stage。
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
- `buildPuzzleRuntimeWorkFromSession(session, owner)`:从拼图 Agent session 构造可进入 runtime 的 draft `PuzzleWorkSummary`,缺草稿、缺 profile 或缺封面时返回 `null`。
|
||||
- `buildSquareHoleProfileFromSession(session)`:从方洞挑战 Agent session draft 构造草稿 `SquareHoleWorkProfile`,缺 session、缺 draft 或缺 profileId 时返回 `null`。
|
||||
- `buildVisualNovelSessionFromWorkDetail(work)`:从视觉小说 work detail 恢复 `VisualNovelAgentSessionSnapshot`,供草稿作品架回到结果页继续编辑。
|
||||
- `buildJumpHopPendingSession(item)`:从跳一跳作品架 summary 构造生成中 pending session。
|
||||
- `buildWoodenFishSessionFromWorkDetail(work, fallbackItem?)`:从敲木鱼 work detail 恢复 session,并按 summary / fallback / profileId 决定 sessionId。
|
||||
- `buildWoodenFishPendingSession(item)`:从敲木鱼作品架 summary 构造生成中 pending session。
|
||||
@@ -25,15 +26,16 @@
|
||||
- 拼图 owner 缺省为 `current-user` / `玩家`;`publishReady` 来自 `session.resultPreview?.publishReady`。
|
||||
- 方洞 profile 的 `workId` 与 `profileId` 都来自 draft `profileId`;owner 固定为 `current-user`,`sourceSessionId` 来自 sessionId。
|
||||
- 方洞 profile 的 `updatedAt` 优先 session `updatedAt`,缺失时使用当前时间;`publicationStatus='draft'`、`playCount=0`、`publishedAt=null`,`publishReady` 来自 draft。
|
||||
- 视觉小说恢复 session 的 `sessionId` 优先归一化后的 `sourceSessionId`,为空时回退 `workId`;`status='ready'`,`messages=[]`,`pendingAction=null`,`sourceMode` 来自 draft,`updatedAt` 来自 summary。
|
||||
- 跳一跳 pending sessionId 优先 `sourceSessionId`,缺失时用 `profileId`;素材、路径和 prompt 维持空值兜底。
|
||||
- 敲木鱼 detail sessionId 优先级固定为 `work.summary.sourceSessionId > fallbackItem.sourceSessionId > profileId`。
|
||||
- 敲木鱼 pending session 保持 `floatingWords=['功德 +1']`、素材 / 音效 / 背景为空的旧默认。
|
||||
|
||||
## Depth / Leverage / Locality
|
||||
|
||||
- **Depth**:壳层以少量函数取得恢复用 DTO;ID 优先级、方洞 profile 默认值和 pending draft 字段藏入 Module Implementation。
|
||||
- **Depth**:壳层以少量函数取得恢复用 DTO;ID 优先级、方洞 profile 默认值、视觉小说 session fallback 和 pending draft 字段藏入 Module Implementation。
|
||||
- **Leverage**:后续新增生成中作品恢复或修改 sessionId 规则时,先改 Module 与单测,再保持壳层 Adapter 副作用不变。
|
||||
- **Locality**:拼图、方洞、跳一跳和敲木鱼的恢复映射集中在一个纯测试面,避免在大型壳层顶部继续堆积 DTO 构造。
|
||||
- **Locality**:拼图、方洞、视觉小说、跳一跳和敲木鱼的恢复映射集中在一个纯测试面,避免在大型壳层顶部继续堆积 DTO 构造。
|
||||
|
||||
## 验收
|
||||
|
||||
|
||||
@@ -537,6 +537,7 @@ import {
|
||||
buildJumpHopPendingSession,
|
||||
buildPuzzleRuntimeWorkFromSession,
|
||||
buildSquareHoleProfileFromSession,
|
||||
buildVisualNovelSessionFromWorkDetail,
|
||||
buildWoodenFishPendingSession,
|
||||
buildWoodenFishSessionFromWorkDetail,
|
||||
} from './platformMiniGameSessionMappingModel';
|
||||
@@ -736,22 +737,6 @@ const PUZZLE_DRAFT_GENERATION_POINT_COST = 2;
|
||||
const MATCH3D_DRAFT_GENERATION_POINT_COST = 10;
|
||||
const BARK_BATTLE_DRAFT_GENERATION_POINT_COST = 3;
|
||||
|
||||
function mapVisualNovelWorkDetailToSession(
|
||||
work: VisualNovelWorkDetail,
|
||||
): VisualNovelAgentSessionSnapshot {
|
||||
return {
|
||||
sessionId: work.sourceSessionId?.trim() || work.workId,
|
||||
ownerUserId: work.summary.ownerUserId,
|
||||
sourceMode: work.draft.sourceMode,
|
||||
status: 'ready',
|
||||
messages: [],
|
||||
draft: work.draft,
|
||||
pendingAction: null,
|
||||
createdAt: work.createdAt,
|
||||
updatedAt: work.summary.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
function mergePuzzleWorkSummary(
|
||||
current: PuzzleWorkSummary,
|
||||
updated: PuzzleWorkSummary,
|
||||
@@ -11094,7 +11079,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
try {
|
||||
const { work } = await getVisualNovelWorkDetail(item.profileId);
|
||||
setVisualNovelWork(work);
|
||||
setVisualNovelSession(mapVisualNovelWorkDetailToSession(work));
|
||||
setVisualNovelSession(buildVisualNovelSessionFromWorkDetail(work));
|
||||
enterCreateTab();
|
||||
setSelectionStage('visual-novel-result');
|
||||
} catch (error) {
|
||||
|
||||
@@ -12,6 +12,10 @@ import type {
|
||||
SquareHoleResultDraft,
|
||||
SquareHoleSessionSnapshot,
|
||||
} from '../../../packages/shared/src/contracts/squareHoleAgent';
|
||||
import type {
|
||||
VisualNovelResultDraft,
|
||||
VisualNovelWorkDetail,
|
||||
} from '../../../packages/shared/src/contracts/visualNovel';
|
||||
import type {
|
||||
WoodenFishAudioAsset,
|
||||
WoodenFishImageAsset,
|
||||
@@ -22,6 +26,7 @@ import {
|
||||
buildJumpHopPendingSession,
|
||||
buildPuzzleRuntimeWorkFromSession,
|
||||
buildSquareHoleProfileFromSession,
|
||||
buildVisualNovelSessionFromWorkDetail,
|
||||
buildWoodenFishPendingSession,
|
||||
buildWoodenFishSessionFromWorkDetail,
|
||||
} from './platformMiniGameSessionMappingModel';
|
||||
@@ -222,6 +227,126 @@ function buildSquareHoleSession(
|
||||
};
|
||||
}
|
||||
|
||||
function buildVisualNovelDraft(
|
||||
overrides: Partial<VisualNovelResultDraft> = {},
|
||||
): VisualNovelResultDraft {
|
||||
return {
|
||||
profileId: 'visual-novel-profile-1',
|
||||
workTitle: '雪线电台',
|
||||
workDescription: '旧电台牵出雪夜列车谜案。',
|
||||
workTags: ['雪夜', '电台'],
|
||||
coverImageSrc: '/visual-novel-cover.png',
|
||||
sourceMode: 'idea',
|
||||
sourceAssetIds: ['asset-source-1'],
|
||||
world: {
|
||||
title: '北境终点线',
|
||||
summary: '边境小城与旧电台。',
|
||||
background: '十二年前的雪崩留下夜间广播。',
|
||||
premise: '玩家需要在日出前找出列车停摆的原因。',
|
||||
literaryStyle: '克制冷光感。',
|
||||
playerRole: '临时广播员',
|
||||
defaultTone: '安静紧张',
|
||||
},
|
||||
characters: [
|
||||
{
|
||||
characterId: 'vn-char-1',
|
||||
name: '林遥',
|
||||
gender: '女',
|
||||
role: 'main',
|
||||
appearance: '灰色长外套。',
|
||||
personality: '谨慎敏锐。',
|
||||
tone: '短句多。',
|
||||
background: '旧电台夜班实习生。',
|
||||
relationshipToPlayer: '临时搭档',
|
||||
imageAssets: [],
|
||||
defaultExpression: 'calm',
|
||||
isPlayerVisible: false,
|
||||
},
|
||||
],
|
||||
scenes: [
|
||||
{
|
||||
sceneId: 'vn-scene-1',
|
||||
name: '风雪站台',
|
||||
description: '站灯忽明忽暗。',
|
||||
backgroundImageSrc: null,
|
||||
musicSrc: null,
|
||||
ambientSoundSrc: null,
|
||||
availability: 'opening',
|
||||
phaseIds: ['vn-phase-1'],
|
||||
},
|
||||
],
|
||||
storyPhases: [
|
||||
{
|
||||
phaseId: 'vn-phase-1',
|
||||
title: '重启站台',
|
||||
goal: '确认列车为何停在废弃站台。',
|
||||
summary: '玩家抵达风雪站台。',
|
||||
entryCondition: '开场进入',
|
||||
exitCondition: '找到车长日志',
|
||||
sceneIds: ['vn-scene-1'],
|
||||
characterIds: ['vn-char-1'],
|
||||
suggestedChoices: ['检查广播柜'],
|
||||
},
|
||||
],
|
||||
opening: {
|
||||
sceneId: 'vn-scene-1',
|
||||
narration: '雪落得很慢。',
|
||||
speakerCharacterId: 'vn-char-1',
|
||||
firstDialogue: '你听见了吗?',
|
||||
initialChoices: [
|
||||
{
|
||||
choiceId: 'vn-choice-1',
|
||||
text: '靠近广播柜。',
|
||||
actionHint: 'inspect_radio',
|
||||
},
|
||||
],
|
||||
},
|
||||
runtimeConfig: {
|
||||
textModeEnabled: true,
|
||||
defaultTextMode: false,
|
||||
maxHistoryEntries: 80,
|
||||
maxAssistantStepCountPerTurn: 8,
|
||||
allowFreeTextAction: true,
|
||||
allowHistoryRegeneration: true,
|
||||
attributePanelMode: 'template_config',
|
||||
saveArchiveEnabled: true,
|
||||
},
|
||||
publishReady: true,
|
||||
validationIssues: [],
|
||||
updatedAt: '2026-06-01T13:00:00.000Z',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function buildVisualNovelWorkDetail(
|
||||
overrides: Partial<VisualNovelWorkDetail> = {},
|
||||
): VisualNovelWorkDetail {
|
||||
const draft = buildVisualNovelDraft();
|
||||
return {
|
||||
workId: 'visual-novel-work-1',
|
||||
summary: {
|
||||
runtimeKind: 'visual-novel',
|
||||
profileId: 'visual-novel-profile-1',
|
||||
ownerUserId: 'user-visual-novel-1',
|
||||
title: draft.workTitle,
|
||||
description: draft.workDescription,
|
||||
coverImageSrc: draft.coverImageSrc,
|
||||
tags: draft.workTags,
|
||||
publishStatus: 'draft',
|
||||
publishReady: draft.publishReady,
|
||||
playCount: 0,
|
||||
updatedAt: '2026-06-01T13:30:00.000Z',
|
||||
publishedAt: null,
|
||||
},
|
||||
sourceSessionId: ' visual-novel-session-1 ',
|
||||
authorDisplayName: '视觉小说作者',
|
||||
sourceAssetIds: draft.sourceAssetIds,
|
||||
draft,
|
||||
createdAt: '2026-06-01T12:50:00.000Z',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
const woodenFishImageAsset: WoodenFishImageAsset = {
|
||||
assetId: 'asset-hit',
|
||||
imageSrc: '/hit.png',
|
||||
@@ -427,6 +552,33 @@ describe('platformMiniGameSessionMappingModel', () => {
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
test('builds visual novel recovered session from work detail', () => {
|
||||
const work = buildVisualNovelWorkDetail();
|
||||
|
||||
expect(buildVisualNovelSessionFromWorkDetail(work)).toEqual({
|
||||
sessionId: 'visual-novel-session-1',
|
||||
ownerUserId: 'user-visual-novel-1',
|
||||
sourceMode: 'idea',
|
||||
status: 'ready',
|
||||
messages: [],
|
||||
draft: work.draft,
|
||||
pendingAction: null,
|
||||
createdAt: '2026-06-01T12:50:00.000Z',
|
||||
updatedAt: '2026-06-01T13:30:00.000Z',
|
||||
});
|
||||
});
|
||||
|
||||
test('falls back visual novel recovered session id to work id', () => {
|
||||
expect(
|
||||
buildVisualNovelSessionFromWorkDetail(
|
||||
buildVisualNovelWorkDetail({
|
||||
sourceSessionId: ' ',
|
||||
workId: 'visual-novel-work-fallback',
|
||||
}),
|
||||
).sessionId,
|
||||
).toBe('visual-novel-work-fallback');
|
||||
});
|
||||
|
||||
test('builds wooden fish pending session from work summary', () => {
|
||||
expect(buildWoodenFishPendingSession(buildWoodenFishSummary())).toEqual({
|
||||
sessionId: 'wooden-fish-session-1',
|
||||
|
||||
@@ -3,6 +3,10 @@ import type { PuzzleAgentSessionSnapshot } from '../../../packages/shared/src/co
|
||||
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
|
||||
import type { SquareHoleSessionSnapshot } from '../../../packages/shared/src/contracts/squareHoleAgent';
|
||||
import type { SquareHoleWorkProfile } from '../../../packages/shared/src/contracts/squareHoleWorks';
|
||||
import type {
|
||||
VisualNovelAgentSessionSnapshot,
|
||||
VisualNovelWorkDetail,
|
||||
} from '../../../packages/shared/src/contracts/visualNovel';
|
||||
import type {
|
||||
WoodenFishSessionSnapshotResponse,
|
||||
WoodenFishWorkProfileResponse,
|
||||
@@ -88,6 +92,22 @@ export function buildSquareHoleProfileFromSession(
|
||||
};
|
||||
}
|
||||
|
||||
export function buildVisualNovelSessionFromWorkDetail(
|
||||
work: VisualNovelWorkDetail,
|
||||
): VisualNovelAgentSessionSnapshot {
|
||||
return {
|
||||
sessionId: normalizeCreationUrlValue(work.sourceSessionId) ?? work.workId,
|
||||
ownerUserId: work.summary.ownerUserId,
|
||||
sourceMode: work.draft.sourceMode,
|
||||
status: 'ready',
|
||||
messages: [],
|
||||
draft: work.draft,
|
||||
pendingAction: null,
|
||||
createdAt: work.createdAt,
|
||||
updatedAt: work.summary.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildJumpHopPendingSession(
|
||||
item: JumpHopWorkSummaryResponse,
|
||||
): JumpHopSessionSnapshotResponse {
|
||||
|
||||
Reference in New Issue
Block a user