refactor: 收口视觉小说详情 session 映射

This commit is contained in:
2026-06-04 05:09:49 +08:00
parent 0dc326b79e
commit 20a21ee78b
6 changed files with 183 additions and 24 deletions

View File

@@ -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) {

View File

@@ -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',

View File

@@ -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 {