071faa482c
纳入 AGC Cargo workspace 的统一 rustfmt 检查与格式化入口 完成项目 TypeScript/Prettier 与 Rust 全量格式化 修复 Pingora expected executable 门禁的空白敏感误报 同步开发运维文档与 AGC skill pack 格式化忽略规则
1605 lines
44 KiB
TypeScript
1605 lines
44 KiB
TypeScript
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 { PuzzleClearWorkSummaryResponse } from '../../../packages/shared/src/contracts/puzzleClear';
|
|
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<string, DraftGenerationNotice>;
|
|
|
|
export type PendingDraftShelfState = {
|
|
status: DraftGenerationNoticeStatus;
|
|
seen: boolean;
|
|
updatedAt: string;
|
|
title?: string;
|
|
summary?: string;
|
|
};
|
|
|
|
export type PendingDraftShelfKind = Exclude<CreationWorkShelfKind, 'rpg'>;
|
|
|
|
export type PendingDraftShelfMap = Partial<
|
|
Record<PendingDraftShelfKind, Record<string, PendingDraftShelfState>>
|
|
>;
|
|
|
|
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 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 type JumpHopDraftOpenIntent =
|
|
| {
|
|
type: 'open-published-detail';
|
|
noticeKeys: string[];
|
|
}
|
|
| {
|
|
type: 'active-failed-generation';
|
|
noticeKeys: string[];
|
|
}
|
|
| {
|
|
type: 'restore-generating';
|
|
noticeKeys: string[];
|
|
}
|
|
| {
|
|
type: 'load-detail';
|
|
noticeKeys: string[];
|
|
};
|
|
|
|
export type WoodenFishDraftOpenIntent =
|
|
| {
|
|
type: 'open-published-detail';
|
|
noticeKeys: string[];
|
|
}
|
|
| {
|
|
type: 'active-failed-generation';
|
|
noticeKeys: string[];
|
|
}
|
|
| {
|
|
type: 'restore-generating';
|
|
noticeKeys: string[];
|
|
}
|
|
| {
|
|
type: 'load-detail';
|
|
noticeKeys: string[];
|
|
failureFallbackStage: 'wooden-fish-workspace' | 'wooden-fish-generating';
|
|
};
|
|
|
|
export function buildDraftNoticeKey(kind: CreationWorkShelfKind, id: string) {
|
|
return `${kind}:${id}`;
|
|
}
|
|
|
|
export function collectDraftNoticeKeys(
|
|
kind: CreationWorkShelfKind,
|
|
ids: Array<string | null | undefined>,
|
|
) {
|
|
const keys = new Set<string>();
|
|
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 | null | undefined>,
|
|
): 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<string | null | undefined>,
|
|
status: DraftGenerationNoticeStatus,
|
|
) {
|
|
return collectDraftNoticeKeys(kind, ids).some(
|
|
(key) => notices[key]?.status === status,
|
|
);
|
|
}
|
|
|
|
export function hasUnreadReadyDraftGenerationNotice(
|
|
notices: DraftGenerationNoticeMap,
|
|
kind: CreationWorkShelfKind,
|
|
ids: Array<string | null | undefined>,
|
|
) {
|
|
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 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 buildJumpHopDraftOpenNoticeKeys(
|
|
item: JumpHopWorkSummaryResponse,
|
|
) {
|
|
return collectDraftNoticeKeys('jump-hop', [
|
|
item.workId,
|
|
item.profileId,
|
|
item.sourceSessionId,
|
|
]);
|
|
}
|
|
|
|
export function buildWoodenFishDraftOpenNoticeKeys(
|
|
item: WoodenFishWorkSummaryResponse,
|
|
) {
|
|
return collectDraftNoticeKeys('wooden-fish', [
|
|
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;
|
|
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 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 resolveJumpHopDraftOpenIntent(params: {
|
|
item: JumpHopWorkSummaryResponse;
|
|
notices: DraftGenerationNoticeMap;
|
|
generation: Pick<
|
|
DraftOpenGenerationFacts,
|
|
'activeSessionId' | 'hasActiveGenerationFailure'
|
|
>;
|
|
}): JumpHopDraftOpenIntent {
|
|
const { item, notices, generation } = params;
|
|
const noticeKeys = buildJumpHopDraftOpenNoticeKeys(item);
|
|
|
|
if (item.publicationStatus === 'published') {
|
|
return { type: 'open-published-detail', noticeKeys };
|
|
}
|
|
|
|
const noticeIds = [item.workId, item.profileId, item.sourceSessionId];
|
|
const sourceSessionId = normalizeDraftNoticeId(item.sourceSessionId);
|
|
const hasFailedNotice = hasDraftGenerationNoticeStatus(
|
|
notices,
|
|
'jump-hop',
|
|
noticeIds,
|
|
'failed',
|
|
);
|
|
const activeSessionId = normalizeDraftNoticeId(generation.activeSessionId);
|
|
const isCurrentSession =
|
|
sourceSessionId !== null && sourceSessionId === activeSessionId;
|
|
|
|
if (
|
|
hasFailedNotice &&
|
|
isCurrentSession &&
|
|
generation.hasActiveGenerationFailure
|
|
) {
|
|
return { type: 'active-failed-generation', noticeKeys };
|
|
}
|
|
|
|
if (isPersistedDraftGenerating(item.generationStatus) && !hasFailedNotice) {
|
|
return { type: 'restore-generating', noticeKeys };
|
|
}
|
|
|
|
return { type: 'load-detail', noticeKeys };
|
|
}
|
|
|
|
export function resolveWoodenFishDraftOpenIntent(params: {
|
|
item: WoodenFishWorkSummaryResponse;
|
|
notices: DraftGenerationNoticeMap;
|
|
generation: Pick<
|
|
DraftOpenGenerationFacts,
|
|
'activeSessionId' | 'hasActiveGenerationFailure'
|
|
>;
|
|
}): WoodenFishDraftOpenIntent {
|
|
const { item, notices, generation } = params;
|
|
const noticeKeys = buildWoodenFishDraftOpenNoticeKeys(item);
|
|
|
|
if (item.publicationStatus === 'published') {
|
|
return { type: 'open-published-detail', noticeKeys };
|
|
}
|
|
|
|
const noticeIds = [item.workId, item.profileId, item.sourceSessionId];
|
|
const sourceSessionId =
|
|
normalizeDraftNoticeId(item.sourceSessionId) ??
|
|
normalizeDraftNoticeId(item.profileId);
|
|
const hasFailedNotice = hasDraftGenerationNoticeStatus(
|
|
notices,
|
|
'wooden-fish',
|
|
noticeIds,
|
|
'failed',
|
|
);
|
|
const isCurrentSession =
|
|
sourceSessionId === normalizeDraftNoticeId(generation.activeSessionId);
|
|
|
|
if (
|
|
hasFailedNotice &&
|
|
isCurrentSession &&
|
|
generation.hasActiveGenerationFailure
|
|
) {
|
|
return { type: 'active-failed-generation', noticeKeys };
|
|
}
|
|
|
|
if (isPersistedDraftGenerating(item.generationStatus) && !hasFailedNotice) {
|
|
return { type: 'restore-generating', noticeKeys };
|
|
}
|
|
|
|
return {
|
|
type: 'load-detail',
|
|
noticeKeys,
|
|
failureFallbackStage: hasFailedNotice
|
|
? 'wooden-fish-workspace'
|
|
: 'wooden-fish-generating',
|
|
};
|
|
}
|
|
|
|
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 countUnreadDraftGenerationUpdates(
|
|
notices: DraftGenerationNoticeMap,
|
|
sources: PlatformDraftGenerationVisibleShelfSources,
|
|
) {
|
|
const hasUnreadNotice = (keys: readonly string[]) =>
|
|
keys.some((key) => {
|
|
const notice = notices[key];
|
|
return notice?.status === 'ready' && !notice.seen;
|
|
});
|
|
|
|
return [
|
|
...sources.rpgItems.map((item) =>
|
|
collectDraftNoticeKeys('rpg', [
|
|
item.workId,
|
|
item.sessionId,
|
|
item.profileId,
|
|
]),
|
|
),
|
|
...sources.bigFishItems.map((item) =>
|
|
collectDraftNoticeKeys('big-fish', [item.workId, item.sourceSessionId]),
|
|
),
|
|
...sources.jumpHopItems.map((item) =>
|
|
collectDraftNoticeKeys('jump-hop', [
|
|
item.workId,
|
|
item.profileId,
|
|
item.sourceSessionId,
|
|
]),
|
|
),
|
|
...sources.woodenFishItems.map((item) =>
|
|
collectDraftNoticeKeys('wooden-fish', [
|
|
item.workId,
|
|
item.profileId,
|
|
item.sourceSessionId,
|
|
]),
|
|
),
|
|
...sources.match3dItems.map((item) =>
|
|
collectDraftNoticeKeys('match3d', [
|
|
item.workId,
|
|
item.profileId,
|
|
item.sourceSessionId,
|
|
]),
|
|
),
|
|
...sources.squareHoleItems.map((item) =>
|
|
collectDraftNoticeKeys('square-hole', [
|
|
item.workId,
|
|
item.profileId,
|
|
item.sourceSessionId,
|
|
]),
|
|
),
|
|
...sources.puzzleItems.map((item) =>
|
|
collectDraftNoticeKeys('puzzle', [
|
|
item.workId,
|
|
item.profileId,
|
|
item.sourceSessionId,
|
|
buildPuzzleResultWorkId(item.sourceSessionId),
|
|
buildPuzzleResultProfileId(item.sourceSessionId),
|
|
]),
|
|
),
|
|
...sources.visualNovelItems.map((item) =>
|
|
collectDraftNoticeKeys('visual-novel', [item.profileId]),
|
|
),
|
|
...sources.barkBattleItems.map((item) =>
|
|
collectDraftNoticeKeys('bark-battle', [item.workId, item.draftId]),
|
|
),
|
|
...sources.babyObjectMatchItems.map((item) =>
|
|
collectDraftNoticeKeys('baby-object-match', [
|
|
item.profileId,
|
|
item.draftId,
|
|
]),
|
|
),
|
|
].filter(hasUnreadNotice).length;
|
|
}
|
|
|
|
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<string, PendingDraftShelfState> | 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<string, PendingDraftShelfState> | 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,
|
|
themeText: state.title ?? '跳一跳草稿',
|
|
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 buildPendingPuzzleClearWorks(
|
|
pending: Record<string, PendingDraftShelfState> | undefined,
|
|
existingItems: readonly PuzzleClearWorkSummaryResponse[],
|
|
): PuzzleClearWorkSummaryResponse[] {
|
|
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: 'puzzle-clear',
|
|
workId: `puzzle-clear-work-${sessionId}`,
|
|
profileId: sessionId,
|
|
ownerUserId: '',
|
|
sourceSessionId: sessionId,
|
|
workTitle: '拼消消草稿',
|
|
workDescription:
|
|
state.status === 'failed'
|
|
? '拼消消草稿生成失败,可重新打开处理。'
|
|
: '正在生成拼消消草稿。',
|
|
themePrompt: '',
|
|
coverImageSrc: null,
|
|
publicationStatus: 'draft',
|
|
playCount: 0,
|
|
updatedAt: state.updatedAt,
|
|
publishedAt: null,
|
|
publishReady: false,
|
|
generationStatus,
|
|
};
|
|
});
|
|
}
|
|
|
|
export function buildPendingWoodenFishWorks(
|
|
pending: Record<string, PendingDraftShelfState> | 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<string, PendingDraftShelfState> | 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<string, PendingDraftShelfState> | 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<string, PendingDraftShelfState> | 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<string, PendingDraftShelfState> | 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<string, PendingDraftShelfState> | 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<string | null | undefined>,
|
|
) {
|
|
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;
|
|
}
|