import type { BarkBattleWorkSummary } from '../../../packages/shared/src/contracts/barkBattle'; import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/bigFishWorkSummary'; import type { CustomWorldWorkSummary } from '../../../packages/shared/src/contracts/customWorldWorkSummary'; import type { BabyObjectMatchDraft } from '../../../packages/shared/src/contracts/edutainmentBabyObject'; import type { JumpHopWorkSummaryResponse } from '../../../packages/shared/src/contracts/jumpHop'; 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 type { WoodenFishWorkSummaryResponse } from '../../../packages/shared/src/contracts/woodenFish'; import { type CreationWorkShelfItem, type CreationWorkShelfKind, type CreationWorkShelfRuntimeState, isPersistedPuzzleDraftGenerating, resolvePuzzleWorkCoverImageSrc, } from '../custom-world-home/creationWorkShelf'; import { buildPuzzleResultProfileId, buildPuzzleResultWorkId, } from './platformPuzzleIdentityModel'; export { buildPuzzleResultProfileId, buildPuzzleResultWorkId, } from './platformPuzzleIdentityModel'; export type DraftGenerationNoticeStatus = 'generating' | 'ready' | 'failed'; export type DraftGenerationNotice = { status: DraftGenerationNoticeStatus; seen: boolean; completedAtMs?: number; message?: string; }; export type DraftGenerationNoticeMap = Record; export type PendingDraftShelfState = { status: DraftGenerationNoticeStatus; seen: boolean; updatedAt: string; title?: string; summary?: string; }; export type PendingDraftShelfKind = Exclude; export type PendingDraftShelfMap = Partial< Record> >; export type PendingDraftShelfMetadata = { title?: string | null; summary?: string | null; }; export type PlatformDraftGenerationVisibleShelfSources = { rpgItems: readonly CustomWorldWorkSummary[]; bigFishItems: readonly BigFishWorkSummary[]; jumpHopItems: readonly JumpHopWorkSummaryResponse[]; woodenFishItems: readonly WoodenFishWorkSummaryResponse[]; match3dItems: readonly Match3DWorkSummary[]; squareHoleItems: readonly SquareHoleWorkSummary[]; puzzleItems: readonly PuzzleWorkSummary[]; visualNovelItems: readonly VisualNovelWorkSummary[]; barkBattleItems: readonly BarkBattleWorkSummary[]; babyObjectMatchItems: readonly BabyObjectMatchDraft[]; }; type DraftOpenGenerationFacts = { activeSessionId?: string | null; hasActiveGenerationFailure: boolean; hasActiveGenerationRunning: boolean; hasBackgroundGenerationFailure: boolean; hasBackgroundGenerationRunning: boolean; }; type FailedDraftGenerationSource = 'background' | 'active' | 'restored'; export type PuzzleDraftOpenIntent = | { type: 'open-published-detail'; noticeKeys: string[]; } | { type: 'missing-session'; noticeKeys: string[]; errorMessage: string; } | { type: 'failed-generation'; noticeKeys: string[]; errorMessage: string; source: FailedDraftGenerationSource; } | { type: 'active-generation'; noticeKeys: string[]; } | { type: 'background-generation'; noticeKeys: string[]; } | { type: 'restore-generating'; noticeKeys: string[]; } | { type: 'restore-draft'; noticeKeys: string[]; }; export type Match3DDraftOpenIntent = | { type: 'open-published-detail'; noticeKeys: string[]; } | { type: 'missing-session'; noticeKeys: string[]; errorMessage: string; } | { type: 'ready-unread'; noticeKeys: string[]; } | { type: 'failed-generation'; noticeKeys: string[]; errorMessage: string; source: FailedDraftGenerationSource; } | { type: 'active-generation'; noticeKeys: string[]; } | { type: 'background-generation'; noticeKeys: string[]; } | { type: 'restore-generating'; noticeKeys: string[]; } | { type: 'restore-draft'; noticeKeys: string[]; }; export function buildDraftNoticeKey( kind: CreationWorkShelfKind, id: string, ) { return `${kind}:${id}`; } export function collectDraftNoticeKeys( kind: CreationWorkShelfKind, ids: Array, ) { const keys = new Set(); for (const id of ids) { const normalizedId = id?.trim(); if (normalizedId) { keys.add(buildDraftNoticeKey(kind, normalizedId)); } } return Array.from(keys); } export function normalizeDraftNoticeId(id: string | null | undefined) { return id?.trim() || null; } export function normalizePendingDraftShelfLookupId( kind: PendingDraftShelfKind, id: string | null | undefined, ) { const normalizedId = normalizeDraftNoticeId(id); if (!normalizedId) { return null; } const noticePrefix = `${kind}:`; if (!normalizedId.startsWith(noticePrefix)) { return normalizedId; } return normalizeDraftNoticeId(normalizedId.slice(noticePrefix.length)); } export function createPendingDraftShelfState( status: DraftGenerationNoticeStatus, seen = false, updatedAt = new Date().toISOString(), metadata?: PendingDraftShelfMetadata, ): PendingDraftShelfState { const title = metadata?.title?.trim(); const summary = metadata?.summary?.trim(); return { status, seen, updatedAt, ...(title ? { title } : {}), ...(summary ? { summary } : {}), }; } export function buildDraftFailedShelfSummary(kind: CreationWorkShelfKind) { switch (kind) { case 'puzzle': return '拼图草稿生成失败,可重新打开处理。'; case 'match3d': return '玩法素材生成失败,可重新打开处理。'; case 'big-fish': return '草稿生成失败,可重新打开处理。'; case 'square-hole': return '挑战素材生成失败,可重新打开处理。'; case 'jump-hop': return '跳一跳玩法草稿生成失败,可重新打开处理。'; case 'wooden-fish': return '敲木鱼草稿生成失败,可重新打开处理。'; case 'visual-novel': return '视觉小说草稿生成失败,可重新打开处理。'; case 'bark-battle': return '声浪竞技素材生成失败,可重新打开处理。'; case 'baby-object-match': return '宝贝识物草稿生成失败,可重新打开处理。'; default: return '草稿生成失败,可重新打开处理。'; } } export function buildDraftCompletionDialogSource( kind: CreationWorkShelfKind, ids: Array, ): string { const sourceId = pickDraftCompletionDialogSourceId(ids); switch (kind) { case 'rpg': return formatDraftTaskCompletionSource('RPG 草稿', sourceId); case 'big-fish': return formatDraftTaskCompletionSource('大鱼吃小鱼草稿', sourceId); case 'match3d': return formatDraftTaskCompletionSource('抓大鹅草稿', sourceId); case 'square-hole': return formatDraftTaskCompletionSource('方洞挑战草稿', sourceId); case 'jump-hop': return formatDraftTaskCompletionSource('跳一跳草稿', sourceId); case 'wooden-fish': return formatDraftTaskCompletionSource('敲木鱼草稿', sourceId); case 'puzzle': return formatDraftTaskCompletionSource('拼图草稿', sourceId); case 'visual-novel': return formatDraftTaskCompletionSource('视觉小说草稿', sourceId); case 'bark-battle': return formatDraftTaskCompletionSource('汪汪声浪草稿', sourceId); case 'baby-object-match': return formatDraftTaskCompletionSource('宝贝识物草稿', sourceId); } return formatDraftTaskCompletionSource('创作草稿', sourceId); } export function isDraftShelfSummaryPlaceholder( value: string | null | undefined, ) { const normalized = value?.trim(); if (!normalized) { return true; } return /^(正在生成|.*生成失败,可重新打开处理。$|未填写作品描述$)/u.test( normalized, ); } export function isPersistedDraftGenerating(value: string | null | undefined) { return value?.trim() === 'generating'; } export function isPersistedDraftFailed(value: string | null | undefined) { const normalized = value?.trim(); return normalized === 'failed' || normalized === 'partial_failed'; } export function getGenerationNoticeShelfKeys( item: CreationWorkShelfItem, ): string[] { switch (item.source.kind) { case 'rpg': return collectDraftNoticeKeys('rpg', [ item.id, item.source.item.workId, item.source.item.sessionId, item.source.item.profileId, ]); case 'big-fish': return collectDraftNoticeKeys('big-fish', [ item.id, item.source.item.workId, item.source.item.sourceSessionId, ]); case 'match3d': return collectDraftNoticeKeys('match3d', [ item.id, item.source.item.workId, item.source.item.profileId, item.source.item.sourceSessionId, ]); case 'square-hole': return collectDraftNoticeKeys('square-hole', [ item.id, item.source.item.workId, item.source.item.profileId, item.source.item.sourceSessionId, ]); case 'jump-hop': return collectDraftNoticeKeys('jump-hop', [ item.id, item.source.item.workId, item.source.item.profileId, item.source.item.sourceSessionId, ]); case 'puzzle': return collectDraftNoticeKeys('puzzle', [ item.id, item.source.item.workId, item.source.item.profileId, item.source.item.sourceSessionId, buildPuzzleResultWorkId(item.source.item.sourceSessionId), buildPuzzleResultProfileId(item.source.item.sourceSessionId), ]); case 'visual-novel': return collectDraftNoticeKeys('visual-novel', [ item.id, item.source.item.profileId, ]); case 'baby-object-match': return collectDraftNoticeKeys('baby-object-match', [ item.id, item.source.item.profileId, item.source.item.draftId, ]); case 'bark-battle': return collectDraftNoticeKeys('bark-battle', [ item.id, item.source.item.workId, item.source.item.draftId, ]); case 'wooden-fish': return collectDraftNoticeKeys('wooden-fish', [ item.id, item.source.item.workId, item.source.item.profileId, item.source.item.sourceSessionId, ]); default: return []; } } export function getDraftGenerationNotice( notices: DraftGenerationNoticeMap, keys: readonly string[], ) { for (const key of keys) { const notice = notices[key]; if (notice) { return notice; } } return null; } export function getPendingDraftShelfState( pendingShelfItems: PendingDraftShelfMap, kind: PendingDraftShelfKind, keys: readonly string[], ) { const entries = pendingShelfItems[kind]; if (!entries) { return null; } for (const key of keys) { const normalizedKey = normalizePendingDraftShelfLookupId(kind, key); const pending = normalizedKey ? entries[normalizedKey] : null; if (pending) { return pending; } } return null; } export function hasDraftGenerationNoticeStatus( notices: DraftGenerationNoticeMap, kind: CreationWorkShelfKind, ids: Array, status: DraftGenerationNoticeStatus, ) { return collectDraftNoticeKeys(kind, ids).some( (key) => notices[key]?.status === status, ); } export function hasUnreadReadyDraftGenerationNotice( notices: DraftGenerationNoticeMap, kind: CreationWorkShelfKind, ids: Array, ) { return collectDraftNoticeKeys(kind, ids).some((key) => { const notice = notices[key]; return notice?.status === 'ready' && !notice.seen; }); } export function buildPuzzleDraftOpenNoticeKeys(item: PuzzleWorkSummary) { return collectDraftNoticeKeys('puzzle', [ item.workId, item.profileId, item.sourceSessionId, buildPuzzleResultWorkId(item.sourceSessionId), buildPuzzleResultProfileId(item.sourceSessionId), ]); } export function buildMatch3DDraftOpenNoticeKeys(item: Match3DWorkSummary) { return collectDraftNoticeKeys('match3d', [ item.workId, item.profileId, item.sourceSessionId, ]); } export function resolvePuzzleDraftOpenIntent(params: { item: PuzzleWorkSummary; notices: DraftGenerationNoticeMap; generation: DraftOpenGenerationFacts; }): PuzzleDraftOpenIntent { const { item, notices, generation } = params; const noticeKeys = buildPuzzleDraftOpenNoticeKeys(item); const sourceSessionId = normalizeDraftNoticeId(item.sourceSessionId); if (!sourceSessionId) { if (item.publicationStatus === 'published') { return { type: 'open-published-detail', noticeKeys }; } return { type: 'missing-session', noticeKeys, errorMessage: '这份拼图草稿缺少会话信息,请重新开始创作。', }; } const failedNotice = getDraftGenerationNotice(notices, noticeKeys); const hasFailedNotice = hasDraftGenerationNoticeStatus( notices, 'puzzle', [ item.workId, item.profileId, item.sourceSessionId, buildPuzzleResultWorkId(item.sourceSessionId), buildPuzzleResultProfileId(item.sourceSessionId), ], 'failed', ); const hasGeneratingNotice = hasDraftGenerationNoticeStatus( notices, 'puzzle', [ item.workId, item.profileId, item.sourceSessionId, buildPuzzleResultWorkId(item.sourceSessionId), buildPuzzleResultProfileId(item.sourceSessionId), ], 'generating', ); const noticeErrorMessage = failedNotice?.status === 'failed' ? (failedNotice.message ?? buildDraftFailedShelfSummary('puzzle')) : buildDraftFailedShelfSummary('puzzle'); const isCurrentSession = sourceSessionId === normalizeDraftNoticeId(generation.activeSessionId); if (generation.hasBackgroundGenerationFailure) { return { type: 'failed-generation', noticeKeys, errorMessage: noticeErrorMessage, source: 'background', }; } if (isCurrentSession && generation.hasActiveGenerationFailure) { return { type: 'failed-generation', noticeKeys, errorMessage: noticeErrorMessage, source: 'active', }; } if (hasFailedNotice || isPersistedDraftFailed(item.generationStatus)) { return { type: 'failed-generation', noticeKeys, errorMessage: noticeErrorMessage, source: 'restored', }; } if (isCurrentSession && generation.hasActiveGenerationRunning) { return { type: 'active-generation', noticeKeys }; } if (generation.hasBackgroundGenerationRunning) { return { type: 'background-generation', noticeKeys }; } const isMarkedGenerating = !hasFailedNotice && ((hasGeneratingNotice && !resolvePuzzleWorkCoverImageSrc(item)) || isPersistedPuzzleDraftGenerating(item)); if (isMarkedGenerating) { return { type: 'restore-generating', noticeKeys }; } return { type: 'restore-draft', noticeKeys }; } export function resolveMatch3DDraftOpenIntent(params: { item: Match3DWorkSummary; notices: DraftGenerationNoticeMap; forceDraft?: boolean; generation: DraftOpenGenerationFacts; }): Match3DDraftOpenIntent { const { item, notices, forceDraft = false, generation } = params; const noticeKeys = buildMatch3DDraftOpenNoticeKeys(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 ( hasUnreadReadyDraftGenerationNotice(notices, 'match3d', [ item.workId, item.profileId, item.sourceSessionId, ]) ) { return { type: 'ready-unread', noticeKeys }; } const failedNotice = getDraftGenerationNotice(notices, noticeKeys); const hasFailedNotice = hasDraftGenerationNoticeStatus( notices, 'match3d', [item.workId, item.profileId, item.sourceSessionId], 'failed', ); const noticeErrorMessage = failedNotice?.status === 'failed' ? (failedNotice.message ?? buildDraftFailedShelfSummary('match3d')) : buildDraftFailedShelfSummary('match3d'); const isCurrentSession = sourceSessionId === normalizeDraftNoticeId(generation.activeSessionId); if (generation.hasBackgroundGenerationFailure) { return { type: 'failed-generation', noticeKeys, errorMessage: noticeErrorMessage, source: 'background', }; } if (isCurrentSession && generation.hasActiveGenerationFailure) { return { type: 'failed-generation', noticeKeys, errorMessage: noticeErrorMessage, source: 'active', }; } if (hasFailedNotice) { return { type: 'failed-generation', noticeKeys, errorMessage: noticeErrorMessage, source: 'restored', }; } if (isCurrentSession && generation.hasActiveGenerationRunning) { return { type: 'active-generation', noticeKeys }; } if (generation.hasBackgroundGenerationRunning) { return { type: 'background-generation', noticeKeys }; } if ( hasDraftGenerationNoticeStatus( notices, 'match3d', [item.workId, item.profileId, item.sourceSessionId], 'generating', ) || isPersistedDraftGenerating(item.generationStatus) ) { return { type: 'restore-generating', noticeKeys }; } return { type: 'restore-draft', noticeKeys }; } export function buildCreationWorkShelfRuntimeState(params: { item: CreationWorkShelfItem; notices: DraftGenerationNoticeMap; pendingShelfItems: PendingDraftShelfMap; }): CreationWorkShelfRuntimeState { const { item, notices, pendingShelfItems } = params; const noticeKeys = getGenerationNoticeShelfKeys(item); const notice = getDraftGenerationNotice(notices, noticeKeys); if (notice?.status === 'failed') { const failedSummary = buildDraftFailedShelfSummary(item.source.kind); const pending = item.source.kind === 'rpg' ? null : getPendingDraftShelfState( pendingShelfItems, item.source.kind, noticeKeys, ); const pendingSummary = pending?.summary?.trim(); return { isGenerating: false, hasGenerationFailure: true, generationFailureSummary: failedSummary, hasUnreadUpdate: false, suppressPersistedGenerating: true, titleOverride: item.source.kind === 'puzzle' && item.status === 'draft' && !item.source.item.workTitle?.trim() ? '拼图草稿' : undefined, summaryOverride: isDraftShelfSummaryPlaceholder(item.summary) ? (pendingSummary ?? failedSummary) : undefined, }; } if ( item.source.kind === 'puzzle' && isPersistedDraftFailed(item.source.item.generationStatus) ) { const failedSummary = buildDraftFailedShelfSummary('puzzle'); return { isGenerating: false, hasGenerationFailure: true, generationFailureSummary: failedSummary, hasUnreadUpdate: false, suppressPersistedGenerating: true, titleOverride: item.status === 'draft' && !item.source.item.workTitle?.trim() ? '拼图草稿' : undefined, summaryOverride: isDraftShelfSummaryPlaceholder(item.summary) ? failedSummary : undefined, }; } const isNoticeGenerating = notice?.status === 'generating' && (item.source.kind !== 'puzzle' || !resolvePuzzleWorkCoverImageSrc(item.source.item)); return { isGenerating: isNoticeGenerating || item.isGenerating === true, hasUnreadUpdate: notice?.status === 'ready' && !notice.seen, }; } export function collectVisibleDraftNoticeKeys( sources: PlatformDraftGenerationVisibleShelfSources, ) { return [ ...sources.rpgItems.flatMap((item) => collectDraftNoticeKeys('rpg', [ item.workId, item.sessionId, item.profileId, ]), ), ...sources.bigFishItems.flatMap((item) => collectDraftNoticeKeys('big-fish', [item.workId, item.sourceSessionId]), ), ...sources.jumpHopItems.flatMap((item) => collectDraftNoticeKeys('jump-hop', [ item.workId, item.profileId, item.sourceSessionId, ]), ), ...sources.woodenFishItems.flatMap((item) => collectDraftNoticeKeys('wooden-fish', [ item.workId, item.profileId, item.sourceSessionId, ]), ), ...sources.match3dItems.flatMap((item) => collectDraftNoticeKeys('match3d', [ item.workId, item.profileId, item.sourceSessionId, ]), ), ...sources.squareHoleItems.flatMap((item) => collectDraftNoticeKeys('square-hole', [ item.workId, item.profileId, item.sourceSessionId, ]), ), ...sources.puzzleItems.flatMap((item) => collectDraftNoticeKeys('puzzle', [ item.workId, item.profileId, item.sourceSessionId, buildPuzzleResultWorkId(item.sourceSessionId), buildPuzzleResultProfileId(item.sourceSessionId), ]), ), ...sources.visualNovelItems.flatMap((item) => collectDraftNoticeKeys('visual-novel', [item.profileId]), ), ...sources.barkBattleItems.flatMap((item) => collectDraftNoticeKeys('bark-battle', [item.workId, item.draftId]), ), ...sources.babyObjectMatchItems.flatMap((item) => collectDraftNoticeKeys('baby-object-match', [ item.profileId, item.draftId, ]), ), ]; } export function hasUnreadDraftGenerationUpdates( notices: DraftGenerationNoticeMap, visibleKeys: readonly string[], ) { return visibleKeys.some((key) => { const notice = notices[key]; return notice?.status === 'ready' && !notice.seen; }); } export function mergeBigFishWorkSummary( current: BigFishWorkSummary, updated: BigFishWorkSummary, ): BigFishWorkSummary { return current.sourceSessionId === updated.sourceSessionId ? updated : current; } export function mergePuzzleWorkSummary( current: PuzzleWorkSummary, updated: PuzzleWorkSummary, ): PuzzleWorkSummary { return current.profileId === updated.profileId ? updated : current; } export function buildPendingBigFishWorks( pending: Record | undefined, existingItems: readonly BigFishWorkSummary[], ): BigFishWorkSummary[] { if (!pending) { return []; } return Object.entries(pending) .filter(([sessionId]) => existingItems.every((item) => item.sourceSessionId !== sessionId), ) .map(([sessionId, state]) => { const isFailed = state.status === 'failed'; return { workId: `big-fish-work-${sessionId}`, sourceSessionId: sessionId, ownerUserId: '', authorDisplayName: '', title: '大鱼吃小鱼草稿', subtitle: isFailed ? '生成失败待重试' : '草稿生成中', summary: isFailed ? '草稿生成失败,可重新打开处理。' : '正在生成玩法草稿。', coverImageSrc: null, status: 'draft', updatedAt: state.updatedAt, publishedAt: null, publishReady: false, levelCount: 0, levelMainImageReadyCount: 0, levelMotionReadyCount: 0, backgroundReady: false, playCount: 0, remixCount: 0, likeCount: 0, }; }); } export function buildPendingJumpHopWorks( pending: Record | undefined, existingItems: readonly JumpHopWorkSummaryResponse[], ): JumpHopWorkSummaryResponse[] { if (!pending) { return []; } return Object.entries(pending) .filter(([sessionId]) => existingItems.every((item) => item.sourceSessionId !== sessionId), ) .map(([sessionId, state]) => { const generationStatus = state.status === 'failed' ? 'failed' : state.status === 'generating' ? 'generating' : 'ready'; return { runtimeKind: 'jump-hop', workId: `jump-hop-work-${sessionId}`, profileId: `jump-hop-profile-${sessionId}`, ownerUserId: '', sourceSessionId: sessionId, workTitle: '跳一跳草稿', workDescription: state.status === 'failed' ? '跳一跳玩法草稿生成失败,可重新打开处理。' : '正在生成跳一跳玩法草稿。', themeTags: [], difficulty: 'standard', stylePreset: 'minimal-blocks', coverImageSrc: null, publicationStatus: 'draft', playCount: 0, updatedAt: state.updatedAt, publishedAt: null, publishReady: false, generationStatus, }; }); } export function buildPendingWoodenFishWorks( pending: Record | undefined, existingItems: readonly WoodenFishWorkSummaryResponse[], ): WoodenFishWorkSummaryResponse[] { if (!pending) { return []; } return Object.entries(pending) .filter(([sessionId]) => existingItems.every((item) => item.sourceSessionId !== sessionId), ) .map(([sessionId, state]) => { const generationStatus = state.status === 'failed' ? 'failed' : state.status === 'generating' ? 'generating' : 'ready'; return { runtimeKind: 'wooden-fish', workId: `wooden-fish-work-${sessionId}`, profileId: sessionId, ownerUserId: '', sourceSessionId: sessionId, workTitle: '敲木鱼草稿', workDescription: state.status === 'failed' ? '敲木鱼草稿生成失败,可重新打开处理。' : '正在生成敲木鱼草稿。', themeTags: ['敲木鱼'], coverImageSrc: null, publicationStatus: 'draft', playCount: 0, updatedAt: state.updatedAt, publishedAt: null, publishReady: false, generationStatus, }; }); } export function buildPendingMatch3DWorks( pending: Record | undefined, existingItems: readonly Match3DWorkSummary[], ): Match3DWorkSummary[] { if (!pending) { return []; } return Object.entries(pending) .filter(([sessionId]) => existingItems.every((item) => item.sourceSessionId !== sessionId), ) .map(([sessionId, state]) => { const themeText = state.summary?.trim() || state.title?.trim() || ''; const fallbackSummary = state.status === 'failed' ? '玩法素材生成失败,可重新打开处理。' : '正在生成玩法素材。'; return { workId: `match3d-work-${sessionId}`, profileId: sessionId, ownerUserId: '', sourceSessionId: sessionId, gameName: '抓大鹅草稿', themeText, summary: themeText || fallbackSummary, tags: [], coverImageSrc: null, referenceImageSrc: null, clearCount: 0, difficulty: 0, publicationStatus: 'draft', playCount: 0, updatedAt: state.updatedAt, publishedAt: null, publishReady: false, generationStatus: state.status === 'failed' ? 'failed' : state.status === 'generating' ? 'generating' : 'ready', generatedItemAssets: [], }; }); } export function buildPendingSquareHoleWorks( pending: Record | undefined, existingItems: readonly SquareHoleWorkSummary[], ): SquareHoleWorkSummary[] { if (!pending) { return []; } return Object.entries(pending) .filter(([sessionId]) => existingItems.every((item) => item.sourceSessionId !== sessionId), ) .map(([sessionId, state]) => ({ workId: `square-hole-work-${sessionId}`, profileId: sessionId, ownerUserId: '', sourceSessionId: sessionId, gameName: '方洞挑战草稿', themeText: '', twistRule: '', summary: state.status === 'failed' ? '挑战素材生成失败,可重新打开处理。' : '正在生成挑战素材。', tags: [], coverImageSrc: null, backgroundPrompt: '', backgroundImageSrc: null, shapeOptions: [], holeOptions: [], shapeCount: 0, difficulty: 0, publicationStatus: 'draft', playCount: 0, updatedAt: state.updatedAt, publishedAt: null, publishReady: false, })); } export function buildPendingPuzzleWorks( pending: Record | undefined, existingItems: readonly PuzzleWorkSummary[], ): PuzzleWorkSummary[] { if (!pending) { return []; } return Object.entries(pending) .filter(([sessionId]) => existingItems.every((item) => item.sourceSessionId !== sessionId), ) .map(([sessionId, state]) => { const profileId = buildPuzzleResultProfileId(sessionId) ?? `puzzle-profile-${sessionId}`; const title = state.title?.trim() || '拼图草稿'; const summary = state.summary?.trim() || (state.status === 'failed' ? '拼图草稿生成失败,可重新打开处理。' : '正在生成拼图草稿。'); return { workId: buildPuzzleResultWorkId(sessionId) ?? `puzzle-work-${sessionId}`, profileId, ownerUserId: '', sourceSessionId: sessionId, authorDisplayName: '', workTitle: title, workDescription: summary, levelName: title, summary, themeTags: [], coverImageSrc: null, coverAssetId: null, publicationStatus: 'draft', updatedAt: state.updatedAt, publishedAt: null, playCount: 0, remixCount: 0, likeCount: 0, publishReady: false, generationStatus: state.status === 'generating' ? 'generating' : state.status === 'failed' ? 'failed' : 'ready', levels: [], }; }); } export function buildPendingVisualNovelWorks( pending: Record | undefined, existingItems: readonly VisualNovelWorkSummary[], ): VisualNovelWorkSummary[] { if (!pending) { return []; } return Object.entries(pending) .filter(([profileId]) => existingItems.every((item) => item.profileId !== profileId), ) .map(([profileId, state]) => ({ runtimeKind: 'visual-novel', profileId, ownerUserId: '', title: '视觉小说草稿', description: state.status === 'failed' ? '视觉小说草稿生成失败,可重新打开处理。' : '正在生成视觉小说草稿。', coverImageSrc: null, tags: [], publishStatus: 'draft', publishReady: false, playCount: 0, updatedAt: state.updatedAt, publishedAt: null, })); } export function buildPendingBarkBattleWorks( pending: Record | undefined, existingItems: readonly BarkBattleWorkSummary[], ): BarkBattleWorkSummary[] { if (!pending) { return []; } return Object.entries(pending) .filter(([id]) => existingItems.every((item) => item.workId !== id && item.draftId !== id), ) .map(([id, state]) => ({ workId: id, draftId: id, ownerUserId: '', authorDisplayName: '', title: '汪汪声浪草稿', summary: state.status === 'failed' ? '声浪竞技素材生成失败,可重新打开处理。' : '正在生成声浪竞技素材。', themeDescription: '', playerImageDescription: '', opponentImageDescription: '', onomatopoeia: [], playerCharacterImageSrc: null, opponentCharacterImageSrc: null, uiBackgroundImageSrc: null, difficultyPreset: 'normal', status: 'draft', generationStatus: state.status === 'generating' ? 'pending_assets' : state.status === 'failed' ? 'partial_failed' : 'ready', publishReady: false, playCount: 0, updatedAt: state.updatedAt, publishedAt: null, })); } function pickDraftCompletionDialogSourceId( ids: Array, ) { const normalizedIds = ids .map((id) => id?.trim() ?? '') .filter((id) => Boolean(id)); return ( normalizedIds.find((id) => /session/i.test(id)) ?? normalizedIds.find((id) => /work/i.test(id)) ?? normalizedIds.find((id) => /draft/i.test(id)) ?? normalizedIds.find((id) => /run/i.test(id)) ?? normalizedIds.find((id) => /profile/i.test(id)) ?? normalizedIds[0] ?? null ); } function formatDraftTaskCompletionSource(label: string, id?: string | null) { const normalizedId = id?.trim(); return normalizedId ? `${label} ${normalizedId}` : label; }