fix: polish bark battle creation flow
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -22,6 +22,7 @@ import {
|
||||
formatPlatformWorkDisplayName,
|
||||
formatPlatformWorkDisplayTags,
|
||||
formatPlatformWorldTime,
|
||||
isBarkBattleGalleryEntry,
|
||||
isEdutainmentGalleryEntry,
|
||||
type PlatformPublicGalleryCard,
|
||||
resolvePlatformPublicWorkCode,
|
||||
@@ -67,6 +68,9 @@ function getSourceLabel(entry: PlatformPublicGalleryCard) {
|
||||
if ('sourceType' in entry && entry.sourceType === 'visual-novel') {
|
||||
return '视觉小说';
|
||||
}
|
||||
if (isBarkBattleGalleryEntry(entry)) {
|
||||
return '汪汪声浪';
|
||||
}
|
||||
if (isEdutainmentGalleryEntry(entry)) {
|
||||
return entry.templateName;
|
||||
}
|
||||
|
||||
108
src/components/platform-entry/barkBattleWorkCache.test.ts
Normal file
108
src/components/platform-entry/barkBattleWorkCache.test.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { expect, test } from 'vitest';
|
||||
|
||||
import type { BarkBattleWorkSummary } from '../../../packages/shared/src/contracts/barkBattle';
|
||||
import {
|
||||
mergeBarkBattleWorksByWorkId,
|
||||
mergeBarkBattleWorkSummary,
|
||||
shouldPreserveLocalBarkBattleWorkOnRefresh,
|
||||
} from './barkBattleWorkCache';
|
||||
|
||||
function buildBarkBattleWork(
|
||||
overrides: Partial<BarkBattleWorkSummary> = {},
|
||||
): BarkBattleWorkSummary {
|
||||
return {
|
||||
workId: 'BB-cache-race-12345678',
|
||||
draftId: 'bark-battle-draft-1',
|
||||
ownerUserId: 'user-1',
|
||||
authorDisplayName: '测试玩家',
|
||||
title: '汪汪测试杯',
|
||||
summary: '测试声浪赛',
|
||||
themeDescription: '阳光草坪声浪竞技场',
|
||||
playerImageDescription: '戴红色围巾的柯基选手',
|
||||
opponentImageDescription: '蓝色护目镜哈士奇对手',
|
||||
playerCharacterImageSrc: '/generated-bark-battle/player.png',
|
||||
opponentCharacterImageSrc: '/generated-bark-battle/opponent.png',
|
||||
uiBackgroundImageSrc: '/generated-bark-battle/background.png',
|
||||
difficultyPreset: 'normal',
|
||||
status: 'draft',
|
||||
generationStatus: 'ready',
|
||||
publishReady: true,
|
||||
playCount: 0,
|
||||
updatedAt: '2026-05-21T10:00:00.000Z',
|
||||
publishedAt: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
test('preserves local published bark battle when refresh only returns same work draft', () => {
|
||||
const published = buildBarkBattleWork({
|
||||
status: 'published',
|
||||
playCount: 3,
|
||||
updatedAt: '2026-05-21T10:02:00.000Z',
|
||||
publishedAt: '2026-05-21T10:02:00.000Z',
|
||||
});
|
||||
const refreshedDraft = buildBarkBattleWork({
|
||||
status: 'draft',
|
||||
playCount: 0,
|
||||
updatedAt: '2026-05-21T10:01:00.000Z',
|
||||
publishedAt: null,
|
||||
});
|
||||
|
||||
expect(shouldPreserveLocalBarkBattleWorkOnRefresh(published, [refreshedDraft])).toBe(
|
||||
true,
|
||||
);
|
||||
|
||||
const [merged] = mergeBarkBattleWorksByWorkId([refreshedDraft, published]);
|
||||
|
||||
expect(merged?.status).toBe('published');
|
||||
expect(merged?.publishedAt).toBe('2026-05-21T10:02:00.000Z');
|
||||
expect(merged?.playCount).toBe(3);
|
||||
});
|
||||
|
||||
test('does not let later draft cache updates downgrade an existing published bark battle', () => {
|
||||
const published = buildBarkBattleWork({
|
||||
status: 'published',
|
||||
playCount: 4,
|
||||
updatedAt: '2026-05-21T10:03:00.000Z',
|
||||
publishedAt: '2026-05-21T10:03:00.000Z',
|
||||
});
|
||||
const staleDraft = buildBarkBattleWork({
|
||||
title: '旧草稿标题',
|
||||
status: 'draft',
|
||||
playCount: 0,
|
||||
updatedAt: '2026-05-21T10:01:00.000Z',
|
||||
publishedAt: null,
|
||||
});
|
||||
|
||||
const merged = mergeBarkBattleWorkSummary(published, staleDraft);
|
||||
|
||||
expect(merged.status).toBe('published');
|
||||
expect(merged.title).toBe('汪汪测试杯');
|
||||
expect(merged.playCount).toBe(4);
|
||||
expect(merged.publishedAt).toBe('2026-05-21T10:03:00.000Z');
|
||||
});
|
||||
|
||||
test('preserves local ready bark battle draft when refresh has not returned it yet', () => {
|
||||
const readyDraft = buildBarkBattleWork({
|
||||
status: 'draft',
|
||||
generationStatus: 'ready',
|
||||
publishReady: true,
|
||||
playerCharacterImageSrc: '/generated-bark-battle/player-ready.png',
|
||||
opponentCharacterImageSrc: '/generated-bark-battle/opponent-ready.png',
|
||||
uiBackgroundImageSrc: '/generated-bark-battle/background-ready.png',
|
||||
});
|
||||
|
||||
expect(shouldPreserveLocalBarkBattleWorkOnRefresh(readyDraft, [])).toBe(true);
|
||||
|
||||
const merged = mergeBarkBattleWorksByWorkId([
|
||||
...[],
|
||||
...(shouldPreserveLocalBarkBattleWorkOnRefresh(readyDraft, [])
|
||||
? [readyDraft]
|
||||
: []),
|
||||
]);
|
||||
|
||||
expect(merged).toHaveLength(1);
|
||||
expect(merged[0]?.workId).toBe('BB-cache-race-12345678');
|
||||
expect(merged[0]?.generationStatus).toBe('ready');
|
||||
});
|
||||
|
||||
112
src/components/platform-entry/barkBattleWorkCache.ts
Normal file
112
src/components/platform-entry/barkBattleWorkCache.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import type { PublicUserSummary } from '../../../packages/shared/src/contracts/auth';
|
||||
import type {
|
||||
BarkBattleDraftConfig,
|
||||
BarkBattleGenerationStatus as SharedBarkBattleGenerationStatus,
|
||||
BarkBattleWorkSummary,
|
||||
} from '../../../packages/shared/src/contracts/barkBattle';
|
||||
|
||||
export type BarkBattleGenerationStatus = SharedBarkBattleGenerationStatus;
|
||||
|
||||
export function mergeBarkBattleWorkSummary(
|
||||
current: BarkBattleWorkSummary,
|
||||
updated: BarkBattleWorkSummary,
|
||||
): BarkBattleWorkSummary {
|
||||
if (current.workId !== updated.workId) {
|
||||
return current;
|
||||
}
|
||||
|
||||
if (current.status === 'published' && updated.status !== 'published') {
|
||||
return {
|
||||
...updated,
|
||||
...current,
|
||||
playCount: current.playCount ?? updated.playCount,
|
||||
recentPlayCount7d: current.recentPlayCount7d ?? updated.recentPlayCount7d,
|
||||
updatedAt: current.updatedAt || updated.updatedAt,
|
||||
publishedAt: current.publishedAt ?? updated.publishedAt,
|
||||
};
|
||||
}
|
||||
|
||||
return { ...current, ...updated };
|
||||
}
|
||||
|
||||
export function hasBarkBattleSummaryRequiredImages(item: BarkBattleWorkSummary) {
|
||||
return Boolean(
|
||||
item.playerCharacterImageSrc?.trim() &&
|
||||
item.opponentCharacterImageSrc?.trim() &&
|
||||
item.uiBackgroundImageSrc?.trim(),
|
||||
);
|
||||
}
|
||||
|
||||
export function shouldPreserveLocalBarkBattleWorkOnRefresh(
|
||||
item: BarkBattleWorkSummary,
|
||||
refreshed: readonly BarkBattleWorkSummary[],
|
||||
) {
|
||||
if (item.status === 'published') {
|
||||
return !refreshed.some(
|
||||
(entry) => entry.workId === item.workId && entry.status === 'published',
|
||||
);
|
||||
}
|
||||
if (refreshed.some((entry) => entry.workId === item.workId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 中文注释:Bark Battle 创建/生成完成/保存后会先把本地摘要塞进作品架,
|
||||
// 后端 /works 读模型可能短暂落后;只要刷新结果还没有同 workId,就保留本地草稿,
|
||||
// 避免 ready 且三图齐全的草稿在刷新窗口期从“我的草稿”里消失。
|
||||
return true;
|
||||
}
|
||||
|
||||
export function buildBarkBattleWorkSummaryFromDraft(
|
||||
draft: BarkBattleDraftConfig,
|
||||
user: PublicUserSummary | null | undefined,
|
||||
generationStatus: BarkBattleGenerationStatus = 'pending_assets',
|
||||
): BarkBattleWorkSummary {
|
||||
const workId = draft.workId?.trim() || draft.draftId;
|
||||
return {
|
||||
workId,
|
||||
draftId: draft.draftId,
|
||||
ownerUserId: user?.id ?? '',
|
||||
authorDisplayName: user?.displayName ?? '创作者',
|
||||
title: draft.title,
|
||||
summary: draft.description ?? '',
|
||||
themeDescription: draft.themeDescription,
|
||||
playerImageDescription: draft.playerImageDescription,
|
||||
opponentImageDescription: draft.opponentImageDescription,
|
||||
onomatopoeia: draft.onomatopoeia,
|
||||
playerCharacterImageSrc: draft.playerCharacterImageSrc ?? null,
|
||||
opponentCharacterImageSrc: draft.opponentCharacterImageSrc ?? null,
|
||||
uiBackgroundImageSrc: draft.uiBackgroundImageSrc ?? null,
|
||||
difficultyPreset: draft.difficultyPreset,
|
||||
status: 'draft',
|
||||
generationStatus,
|
||||
publishReady: Boolean(
|
||||
draft.playerCharacterImageSrc?.trim() &&
|
||||
draft.opponentCharacterImageSrc?.trim() &&
|
||||
draft.uiBackgroundImageSrc?.trim(),
|
||||
),
|
||||
playCount: 0,
|
||||
updatedAt: draft.updatedAt,
|
||||
publishedAt: null,
|
||||
};
|
||||
}
|
||||
|
||||
export function mergeBarkBattleWorksByWorkId(
|
||||
items: readonly BarkBattleWorkSummary[],
|
||||
): BarkBattleWorkSummary[] {
|
||||
const byWorkId = new Map<string, BarkBattleWorkSummary>();
|
||||
for (const item of items) {
|
||||
const current = byWorkId.get(item.workId);
|
||||
if (!current) {
|
||||
byWorkId.set(item.workId, item);
|
||||
continue;
|
||||
}
|
||||
if (current.status !== 'published' && item.status === 'published') {
|
||||
byWorkId.set(item.workId, { ...current, ...item });
|
||||
continue;
|
||||
}
|
||||
if (current.status === item.status || current.status === 'published') {
|
||||
byWorkId.set(item.workId, mergeBarkBattleWorkSummary(current, item));
|
||||
}
|
||||
}
|
||||
return Array.from(byWorkId.values());
|
||||
}
|
||||
@@ -31,6 +31,7 @@ export type SelectionStage =
|
||||
| 'square-hole-generating'
|
||||
| 'square-hole-result'
|
||||
| 'square-hole-runtime'
|
||||
| 'bark-battle-generating'
|
||||
| 'bark-battle-result'
|
||||
| 'bark-battle-runtime'
|
||||
| 'creative-agent-workspace'
|
||||
|
||||
Reference in New Issue
Block a user