This commit is contained in:
2026-05-14 21:33:34 +08:00
193 changed files with 17051 additions and 1203 deletions

View File

@@ -20,6 +20,7 @@ export interface PlatformEntryCreationTypeModalProps {
onSelectSquareHole: () => void;
onSelectPuzzle: () => void;
onSelectCreativeAgent: () => void;
onSelectBarkBattle: () => void;
onSelectVisualNovel: () => void;
onSelectBabyObjectMatch: () => void;
}
@@ -101,6 +102,7 @@ export function PlatformEntryCreationTypeModal({
onSelectSquareHole,
onSelectPuzzle,
onSelectCreativeAgent,
onSelectBarkBattle,
onSelectVisualNovel,
onSelectBabyObjectMatch,
}: PlatformEntryCreationTypeModalProps) {
@@ -146,6 +148,9 @@ export function PlatformEntryCreationTypeModal({
if (item.id === 'creative-agent') {
onSelectCreativeAgent();
}
if (item.id === 'bark-battle') {
onSelectBarkBattle();
}
if (item.id === 'visual-novel') {
onSelectVisualNovel();
}

View File

@@ -13,6 +13,10 @@ import {
} from 'react';
import type { PublicUserSummary } from '../../../packages/shared/src/contracts/auth';
import type {
BarkBattleConfigEditorPayload,
BarkBattlePublishedConfig,
} from '../../../packages/shared/src/contracts/barkBattle';
import type {
BigFishRuntimeSnapshotResponse,
BigFishSessionSnapshotResponse,
@@ -113,6 +117,10 @@ import {
getPublicAuthUserByCode,
getPublicAuthUserById,
} from '../../services/authService';
import {
createBarkBattleDraft,
publishBarkBattleWork,
} from '../../services/bark-battle-creation';
import {
createBigFishCreationSession,
executeBigFishCreationAction,
@@ -150,8 +158,11 @@ import {
} from '../../services/customWorldAgentUiState';
import {
createBabyObjectMatchDraft,
deleteLocalBabyObjectMatchDraft,
hasBabyObjectMatchPlaceholderAssets,
listLocalBabyObjectMatchDrafts,
publishBabyObjectMatchWork,
regenerateBabyObjectMatchDraftAssets,
saveBabyObjectMatchDraft,
} from '../../services/edutainment-baby-object';
import { match3dCreationClient } from '../../services/match3d-creation';
@@ -323,6 +334,7 @@ import type { PlatformCreationTypeId } from './platformEntryCreationTypes';
import {
derivePlatformCreationTypes,
getVisiblePlatformCreationTypes,
isPlatformCreationTypeOpen,
isPlatformCreationTypeVisible,
} from './platformEntryCreationTypes';
import {
@@ -1933,6 +1945,20 @@ const SquareHoleRuntimeShell = lazy(async () => {
};
});
const BarkBattleConfigEditor = lazy(async () => {
const module = await import('../bark-battle-creation/BarkBattleConfigEditor');
return {
default: module.BarkBattleConfigEditor,
};
});
const BarkBattleRuntimeShell = lazy(async () => {
const module = await import('../../games/bark-battle/ui/BarkBattleRuntimeShell');
return {
default: module.BarkBattleRuntimeShell,
};
});
const CustomWorldCreationHub = lazy(async () => {
const module = await import('../custom-world-home/CustomWorldCreationHub');
return {
@@ -1990,6 +2016,15 @@ const BabyObjectMatchRuntimeShell = lazy(async () => {
};
});
const BabyLoveDrawingRuntimeShell = lazy(async () => {
const module = await import(
'../edutainment-runtime/BabyLoveDrawingRuntimeShell'
);
return {
default: module.BabyLoveDrawingRuntimeShell,
};
});
const VisualNovelResultView = lazy(async () => {
const module = await import('../visual-novel-result/VisualNovelResultView');
return {
@@ -2142,6 +2177,10 @@ export function PlatformEntryFlowShellImpl({
useState(false);
const [squareHoleGenerationState, setSquareHoleGenerationState] =
useState<MiniGameDraftGenerationState | null>(null);
const [barkBattlePublishedConfig, setBarkBattlePublishedConfig] =
useState<BarkBattlePublishedConfig | null>(null);
const [barkBattleError, setBarkBattleError] = useState<string | null>(null);
const [isBarkBattleBusy, setIsBarkBattleBusy] = useState(false);
const [bigFishRun, setBigFishRun] =
useState<BigFishRuntimeSnapshotResponse | null>(null);
const [bigFishRuntimeShare, setBigFishRuntimeShare] = useState<{
@@ -2328,6 +2367,10 @@ export function PlatformEntryFlowShellImpl({
creationEntryTypes,
'baby-object-match',
);
const isVisualNovelCreationOpen = isPlatformCreationTypeOpen(
creationEntryTypes,
'visual-novel',
);
const [profilePlayStats, setProfilePlayStats] =
useState<ProfilePlayStatsResponse | null>(null);
const [profilePlayStatsError, setProfilePlayStatsError] = useState<
@@ -2745,6 +2788,12 @@ export function PlatformEntryFlowShellImpl({
}, [resolvePuzzleErrorMessage]);
const refreshVisualNovelShelf = useCallback(async () => {
if (!isVisualNovelCreationOpen) {
setVisualNovelWorks([]);
visualNovelErrorSetterRef.current(null);
return [];
}
setIsVisualNovelLoadingLibrary(true);
try {
@@ -2760,9 +2809,15 @@ export function PlatformEntryFlowShellImpl({
} finally {
setIsVisualNovelLoadingLibrary(false);
}
}, [resolvePuzzleErrorMessage]);
}, [isVisualNovelCreationOpen, resolvePuzzleErrorMessage]);
const refreshVisualNovelGallery = useCallback(async () => {
if (!isVisualNovelCreationOpen) {
setVisualNovelGalleryEntries([]);
visualNovelErrorSetterRef.current(null);
return [];
}
try {
const galleryResponse = await listVisualNovelGallery();
setVisualNovelGalleryEntries(galleryResponse.works);
@@ -2774,7 +2829,7 @@ export function PlatformEntryFlowShellImpl({
);
return [];
}
}, [resolvePuzzleErrorMessage]);
}, [isVisualNovelCreationOpen, resolvePuzzleErrorMessage]);
const handleRpgDraftGenerationStarted = useCallback(
(sessionId: string) => {
@@ -2802,8 +2857,8 @@ export function PlatformEntryFlowShellImpl({
[markDraftReady, platformBootstrap],
);
const refreshBabyObjectMatchShelf = useCallback(() => {
setBabyObjectMatchDrafts(listLocalBabyObjectMatchDrafts());
const refreshBabyObjectMatchShelf = useCallback(async () => {
setBabyObjectMatchDrafts(await listLocalBabyObjectMatchDrafts());
}, []);
const sessionController = useRpgCreationSessionController({
@@ -3031,7 +3086,7 @@ export function PlatformEntryFlowShellImpl({
...match3dPublicEntries,
...puzzlePublicEntries,
...squareHolePublicEntries,
...visualNovelPublicEntries,
...(isVisualNovelCreationOpen ? visualNovelPublicEntries : []),
...babyObjectMatchPublicEntries,
],
).slice(0, 6);
@@ -3039,6 +3094,7 @@ export function PlatformEntryFlowShellImpl({
babyObjectMatchDrafts,
isBigFishCreationVisible,
isBabyObjectMatchVisible,
isVisualNovelCreationOpen,
bigFishGalleryEntries,
match3dGalleryEntries,
platformBootstrap.publishedGalleryEntries,
@@ -3059,9 +3115,11 @@ export function PlatformEntryFlowShellImpl({
...squareHoleGalleryEntries.map(
mapSquareHoleWorkToPlatformGalleryCard,
),
...visualNovelGalleryEntries.map(
mapVisualNovelWorkToPlatformGalleryCard,
),
...(isVisualNovelCreationOpen
? visualNovelGalleryEntries.map(
mapVisualNovelWorkToPlatformGalleryCard,
)
: []),
...(isBabyObjectMatchVisible
? babyObjectMatchDrafts
.filter((draft) => draft.publicationStatus === 'published')
@@ -3073,6 +3131,7 @@ export function PlatformEntryFlowShellImpl({
babyObjectMatchDrafts,
isBabyObjectMatchVisible,
isBigFishCreationVisible,
isVisualNovelCreationOpen,
bigFishGalleryEntries,
match3dGalleryEntries,
platformBootstrap.publishedGalleryEntries,
@@ -3139,14 +3198,17 @@ export function PlatformEntryFlowShellImpl({
[pendingDraftShelfItems, puzzleWorks],
);
const visualNovelShelfItems = useMemo(
() => [
...buildPendingVisualNovelWorks(
pendingDraftShelfItems['visual-novel'],
visualNovelWorks,
),
...visualNovelWorks,
],
[pendingDraftShelfItems, visualNovelWorks],
() =>
isVisualNovelCreationOpen
? [
...buildPendingVisualNovelWorks(
pendingDraftShelfItems['visual-novel'],
visualNovelWorks,
),
...visualNovelWorks,
]
: [],
[isVisualNovelCreationOpen, pendingDraftShelfItems, visualNovelWorks],
);
const getCreationWorkShelfState = useCallback(
(item: CreationWorkShelfItem) => {
@@ -4913,7 +4975,7 @@ export function PlatformEntryFlowShellImpl({
try {
const response = await createBabyObjectMatchDraft(payload);
setBabyObjectMatchDraft(response.draft);
refreshBabyObjectMatchShelf();
void refreshBabyObjectMatchShelf();
setBabyObjectMatchGenerationPhase('ready');
setBabyObjectMatchGenerationState((current) =>
current
@@ -5167,6 +5229,15 @@ export function PlatformEntryFlowShellImpl({
return;
}
if (type === 'bark-battle') {
enterCreateTab();
setShowCreationTypeModal(false);
setActiveCreationFormType('bark-battle');
setBarkBattleError(null);
setSelectionStage('bark-battle-config');
return;
}
if (type === 'visual-novel') {
enterCreateTab();
setShowCreationTypeModal(false);
@@ -5196,9 +5267,12 @@ export function PlatformEntryFlowShellImpl({
prepareCreationLaunch,
runProtectedAction,
sessionController,
setActiveCreationFormType,
setBarkBattleError,
setMatch3DError,
setPuzzleCreationError,
setPuzzleError,
setSelectionStage,
setVisualNovelError,
],
);
@@ -5228,6 +5302,37 @@ export function PlatformEntryFlowShellImpl({
squareHoleFlow.leaveFlow();
}, [squareHoleFlow]);
const leaveBarkBattleFlow = useCallback(() => {
setBarkBattlePublishedConfig(null);
setBarkBattleError(null);
setIsBarkBattleBusy(false);
setSelectionStage('platform');
}, [setSelectionStage]);
const publishBarkBattleConfig = useCallback(
async (payload: BarkBattleConfigEditorPayload) => {
setBarkBattleError(null);
setIsBarkBattleBusy(true);
try {
const draft = await createBarkBattleDraft(payload);
const published = await publishBarkBattleWork({
draftId: draft.draftId,
workId: draft.workId,
publishedSnapshot: payload,
});
setBarkBattlePublishedConfig(published);
setSelectionStage('bark-battle-runtime');
} catch (error) {
setBarkBattleError(
resolvePuzzleErrorMessage(error, '发布汪汪声浪大作战作品失败。'),
);
} finally {
setIsBarkBattleBusy(false);
}
},
[resolvePuzzleErrorMessage, setSelectionStage],
);
const leavePuzzleFlow = useCallback(() => {
setPuzzleOperation(null);
setPuzzleRun(null);
@@ -5266,7 +5371,7 @@ export function PlatformEntryFlowShellImpl({
try {
const response = await saveBabyObjectMatchDraft({ draft });
setBabyObjectMatchDraft(response.draft);
refreshBabyObjectMatchShelf();
void refreshBabyObjectMatchShelf();
} catch (error) {
setBabyObjectMatchError(
resolvePuzzleErrorMessage(error, '保存宝贝识物草稿失败。'),
@@ -5278,14 +5383,52 @@ export function PlatformEntryFlowShellImpl({
[refreshBabyObjectMatchShelf, resolvePuzzleErrorMessage],
);
const ensureBabyObjectMatchGeneratedAssets = useCallback(
async (draft: BabyObjectMatchDraft) => {
if (!hasBabyObjectMatchPlaceholderAssets(draft)) {
return draft;
}
const response = await regenerateBabyObjectMatchDraftAssets(draft);
setBabyObjectMatchDraft(response.draft);
void refreshBabyObjectMatchShelf();
return response.draft;
},
[refreshBabyObjectMatchShelf],
);
const regenerateBabyObjectMatchResultAssets = useCallback(
async (draft: BabyObjectMatchDraft) => {
setBabyObjectMatchError(null);
setIsBabyObjectMatchBusy(true);
try {
await ensureBabyObjectMatchGeneratedAssets(draft);
} catch (error) {
setBabyObjectMatchError(
resolvePuzzleErrorMessage(
error,
'重新生成宝贝识物 image-2 资源失败。',
),
);
} finally {
setIsBabyObjectMatchBusy(false);
}
},
[ensureBabyObjectMatchGeneratedAssets, resolvePuzzleErrorMessage],
);
const publishBabyObjectMatchResultDraft = useCallback(
async (draft: BabyObjectMatchDraft) => {
setBabyObjectMatchError(null);
setIsBabyObjectMatchBusy(true);
try {
const response = await publishBabyObjectMatchWork({ draft });
const generatedDraft = await ensureBabyObjectMatchGeneratedAssets(draft);
const response = await publishBabyObjectMatchWork({
draft: generatedDraft,
});
setBabyObjectMatchDraft(response.draft);
refreshBabyObjectMatchShelf();
void refreshBabyObjectMatchShelf();
openPublishShareModal({
title: response.draft.workTitle,
publicWorkCode:
@@ -5295,13 +5438,17 @@ export function PlatformEntryFlowShellImpl({
});
} catch (error) {
setBabyObjectMatchError(
resolvePuzzleErrorMessage(error, '发布宝贝识物作品失败。'),
resolvePuzzleErrorMessage(
error,
'生成宝贝识物 image-2 资源失败,请重试后再发布。',
),
);
} finally {
setIsBabyObjectMatchBusy(false);
}
},
[
ensureBabyObjectMatchGeneratedAssets,
openPublishShareModal,
refreshBabyObjectMatchShelf,
resolvePuzzleErrorMessage,
@@ -5309,40 +5456,66 @@ export function PlatformEntryFlowShellImpl({
);
const startBabyObjectMatchRuntimeFromDraft = useCallback(
(
async (
draft: BabyObjectMatchDraft,
returnStage: BabyObjectMatchRuntimeReturnStage = 'baby-object-match-result',
options: { embedded?: boolean } = {},
) => {
setBabyObjectMatchDraft(draft);
setBabyObjectMatchFormPayload({
itemAName: draft.itemNames[0],
itemBName: draft.itemNames[1],
});
setBabyObjectMatchRuntimeReturnStage(returnStage);
setBabyObjectMatchError(null);
if (!options.embedded) {
setSelectionStage('baby-object-match-runtime');
const publicWorkCode =
draft.publicationStatus === 'published'
? buildBabyObjectMatchPublicWorkCode(draft.profileId)
: null;
if (publicWorkCode) {
pushAppHistoryPath(
buildPublicWorkStagePath(
'baby-object-match-runtime',
publicWorkCode,
),
);
setIsBabyObjectMatchBusy(true);
try {
const generatedDraft =
await ensureBabyObjectMatchGeneratedAssets(draft);
setBabyObjectMatchDraft(generatedDraft);
setBabyObjectMatchFormPayload({
itemAName: generatedDraft.itemNames[0],
itemBName: generatedDraft.itemNames[1],
});
setBabyObjectMatchRuntimeReturnStage(returnStage);
if (!options.embedded) {
setSelectionStage('baby-object-match-runtime');
const publicWorkCode =
generatedDraft.publicationStatus === 'published'
? buildBabyObjectMatchPublicWorkCode(generatedDraft.profileId)
: null;
if (publicWorkCode) {
pushAppHistoryPath(
buildPublicWorkStagePath(
'baby-object-match-runtime',
publicWorkCode,
),
);
}
}
return true;
} catch (error) {
const message = resolvePuzzleErrorMessage(
error,
'生成宝贝识物 image-2 资源失败,请重试后再试玩。',
);
setBabyObjectMatchError(message);
if (options.embedded) {
setActiveRecommendRuntimeError(message);
}
return false;
} finally {
setIsBabyObjectMatchBusy(false);
}
return true;
},
[setSelectionStage],
[
ensureBabyObjectMatchGeneratedAssets,
resolvePuzzleErrorMessage,
setSelectionStage,
],
);
const startBabyLoveDrawingRuntime = useCallback(() => {
setSelectionStage('baby-love-drawing-runtime');
pushAppHistoryPath('/runtime/baby-love-drawing');
}, [setSelectionStage]);
const resolveBabyObjectMatchRuntimeDraft = useCallback(
(entry: PlatformPublicGalleryCard) => {
async (entry: PlatformPublicGalleryCard) => {
if (!isEdutainmentGalleryEntry(entry)) {
return null;
}
@@ -5351,7 +5524,7 @@ export function PlatformEntryFlowShellImpl({
babyObjectMatchDrafts.find(
(draft) => draft.profileId === entry.profileId,
) ??
listLocalBabyObjectMatchDrafts().find(
(await listLocalBabyObjectMatchDrafts()).find(
(draft) => draft.profileId === entry.profileId,
) ??
null
@@ -5361,12 +5534,12 @@ export function PlatformEntryFlowShellImpl({
);
const startBabyObjectMatchRuntimeFromEntry = useCallback(
(
async (
entry: PlatformPublicGalleryCard,
returnStage: BabyObjectMatchRuntimeReturnStage = 'work-detail',
options: { embedded?: boolean } = {},
) => {
const draft = resolveBabyObjectMatchRuntimeDraft(entry);
const draft = await resolveBabyObjectMatchRuntimeDraft(entry);
if (!draft) {
setPublicWorkDetailError(
'当前宝贝识物作品缺少本地草稿,暂时无法进入玩法。',
@@ -5374,7 +5547,11 @@ export function PlatformEntryFlowShellImpl({
return false;
}
return startBabyObjectMatchRuntimeFromDraft(draft, returnStage, options);
return await startBabyObjectMatchRuntimeFromDraft(
draft,
returnStage,
options,
);
},
[resolveBabyObjectMatchRuntimeDraft, startBabyObjectMatchRuntimeFromDraft],
);
@@ -6995,7 +7172,7 @@ export function PlatformEntryFlowShellImpl({
return;
}
runProtectedAction(() => {
runProtectedAction(async () => {
setIsPublicWorkDetailBusy(true);
setIsPuzzleBusy(true);
setPuzzleError(null);
@@ -7474,6 +7651,67 @@ export function PlatformEntryFlowShellImpl({
],
);
const handleDeleteBabyObjectMatchWork = useCallback(
(work: BabyObjectMatchDraft) => {
if (deletingCreationWorkId) {
return;
}
const noticeKeys = collectDraftNoticeKeys('baby-object-match', [
work.profileId,
work.draftId,
]);
const displayName = work.workTitle.trim() || work.templateName;
requestDeleteCreationWork({
id: work.profileId,
title: displayName,
detail:
work.publicationStatus === 'published'
? '删除后会从你的作品列表和寓教于乐板块中移除。'
: '删除后会从你的作品列表中移除。',
run: () => {
setDeletingCreationWorkId(work.profileId);
setBabyObjectMatchError(null);
void deleteLocalBabyObjectMatchDraft(work.profileId)
.then((nextDrafts) => {
markDraftNoticeSeen(noticeKeys);
setBabyObjectMatchDrafts(nextDrafts);
setBabyObjectMatchDraft((current) =>
current?.profileId === work.profileId ? null : current,
);
if (
babyObjectMatchDraft?.profileId === work.profileId &&
(selectionStage === 'baby-object-match-result' ||
selectionStage === 'baby-object-match-runtime')
) {
enterCreateTab();
setSelectionStage('platform');
}
})
.catch((error) => {
setBabyObjectMatchError(
resolvePuzzleErrorMessage(error, '删除宝贝识物作品失败。'),
);
})
.finally(() => {
setDeletingCreationWorkId(null);
});
},
});
},
[
babyObjectMatchDraft?.profileId,
deletingCreationWorkId,
enterCreateTab,
markDraftNoticeSeen,
requestDeleteCreationWork,
resolvePuzzleErrorMessage,
selectionStage,
setSelectionStage,
],
);
const clearSelectedPublicWorkAuthor = useCallback(() => {
publicWorkAuthorRequestKeyRef.current += 1;
setSelectedPublicWorkAuthor(null);
@@ -8668,7 +8906,7 @@ export function PlatformEntryFlowShellImpl({
if (isEdutainmentGalleryEntry(selectedPublicWorkDetail)) {
setPublicWorkDetailError(null);
startBabyObjectMatchRuntimeFromEntry(
void startBabyObjectMatchRuntimeFromEntry(
selectedPublicWorkDetail,
'work-detail',
);
@@ -8800,9 +9038,13 @@ export function PlatformEntryFlowShellImpl({
{ embedded: true },
);
} else if (isEdutainmentGalleryEntry(entry)) {
started = startBabyObjectMatchRuntimeFromEntry(entry, 'platform', {
embedded: true,
});
started = await startBabyObjectMatchRuntimeFromEntry(
entry,
'platform',
{
embedded: true,
},
);
} else {
started = true;
}
@@ -9360,7 +9602,7 @@ export function PlatformEntryFlowShellImpl({
return;
}
runProtectedAction(() => {
runProtectedAction(async () => {
setPublicWorkDetailError(null);
// 中文注释:自有公开作品必须恢复原草稿,不能复用 remix 复制链路。
@@ -9428,7 +9670,7 @@ export function PlatformEntryFlowShellImpl({
}
if (isEdutainmentGalleryEntry(entry)) {
const matchedDraft = resolveBabyObjectMatchRuntimeDraft(entry);
const matchedDraft = await resolveBabyObjectMatchRuntimeDraft(entry);
if (!matchedDraft) {
setPublicWorkDetailError('这份宝贝识物缺少可编辑草稿。');
return;
@@ -9655,8 +9897,8 @@ export function PlatformEntryFlowShellImpl({
mapVisualNovelWorkToPublicWorkDetail(matchedEntry),
);
};
const tryOpenBabyObjectMatchGalleryEntry = () => {
const entries = listLocalBabyObjectMatchDrafts().filter(
const tryOpenBabyObjectMatchGalleryEntry = async () => {
const entries = (await listLocalBabyObjectMatchDrafts()).filter(
(draft) => draft.publicationStatus === 'published',
);
const matchedDraft = entries.find((draft) => {
@@ -9699,7 +9941,7 @@ export function PlatformEntryFlowShellImpl({
}
if (shouldSearchBabyObjectFirst) {
tryOpenBabyObjectMatchGalleryEntry();
await tryOpenBabyObjectMatchGalleryEntry();
return;
}
@@ -9959,11 +10201,14 @@ export function PlatformEntryFlowShellImpl({
if (isSquareHoleCreationVisible) {
void refreshSquareHoleGallery();
}
void refreshVisualNovelGallery();
if (isVisualNovelCreationOpen) {
void refreshVisualNovelGallery();
}
}
}, [
isBigFishCreationVisible,
isSquareHoleCreationVisible,
isVisualNovelCreationOpen,
refreshBigFishGallery,
refreshMatch3DGallery,
refreshPuzzleGallery,
@@ -9983,11 +10228,14 @@ export function PlatformEntryFlowShellImpl({
if (isSquareHoleCreationVisible) {
void refreshSquareHoleShelf();
}
void refreshVisualNovelShelf();
refreshBabyObjectMatchShelf();
if (isVisualNovelCreationOpen) {
void refreshVisualNovelShelf();
}
void refreshBabyObjectMatchShelf();
}
}, [
isSquareHoleCreationVisible,
isVisualNovelCreationOpen,
platformBootstrap.canReadProtectedData,
platformBootstrap.platformTab,
refreshBabyObjectMatchShelf,
@@ -10030,7 +10278,7 @@ export function PlatformEntryFlowShellImpl({
isMatch3DLoadingLibrary ||
(isSquareHoleCreationVisible && isSquareHoleLoadingLibrary) ||
isPuzzleLoadingLibrary ||
isVisualNovelLoadingLibrary ||
(isVisualNovelCreationOpen && isVisualNovelLoadingLibrary) ||
isBabyObjectMatchBusy
}
error={
@@ -10039,7 +10287,7 @@ export function PlatformEntryFlowShellImpl({
isMatch3DLoadingLibrary ||
(isSquareHoleCreationVisible && isSquareHoleLoadingLibrary) ||
isPuzzleLoadingLibrary ||
isVisualNovelLoadingLibrary ||
(isVisualNovelCreationOpen && isVisualNovelLoadingLibrary) ||
isBabyObjectMatchBusy
? null
: (platformBootstrap.platformError ??
@@ -10049,7 +10297,7 @@ export function PlatformEntryFlowShellImpl({
(isSquareHoleCreationVisible ? squareHoleError : null) ??
puzzleShelfError ??
puzzleError ??
visualNovelError ??
(isVisualNovelCreationOpen ? visualNovelError : null) ??
babyObjectMatchError)
}
onRetry={() => {
@@ -10085,8 +10333,10 @@ export function PlatformEntryFlowShellImpl({
void refreshSquareHoleShelf();
}
void refreshPuzzleShelf();
void refreshVisualNovelShelf();
refreshBabyObjectMatchShelf();
if (isVisualNovelCreationOpen) {
void refreshVisualNovelShelf();
}
void refreshBabyObjectMatchShelf();
}}
createError={
creationEntryConfigError ??
@@ -10096,7 +10346,7 @@ export function PlatformEntryFlowShellImpl({
(isSquareHoleCreationVisible ? squareHoleError : null) ??
puzzleCreationError ??
puzzleError ??
visualNovelError ??
(isVisualNovelCreationOpen ? visualNovelError : null) ??
babyObjectMatchError
}
createBusy={
@@ -10108,8 +10358,8 @@ export function PlatformEntryFlowShellImpl({
isMatch3DBusy ||
(isSquareHoleCreationVisible && isSquareHoleBusy) ||
isPuzzleBusy ||
isVisualNovelBusy ||
isVisualNovelStreamingReply ||
(isVisualNovelCreationOpen && isVisualNovelBusy) ||
(isVisualNovelCreationOpen && isVisualNovelStreamingReply) ||
isBabyObjectMatchBusy
}
entryConfig={creationEntryConfig}
@@ -10206,6 +10456,9 @@ export function PlatformEntryFlowShellImpl({
openBabyObjectMatchDraft(item);
});
}}
onDeleteBabyObjectMatch={(item) => {
handleDeleteBabyObjectMatchWork(item);
}}
visualNovelItems={visualNovelShelfItems}
onOpenVisualNovelDetail={(item) => {
runProtectedAction(() => {
@@ -10440,6 +10693,7 @@ export function PlatformEntryFlowShellImpl({
onOpenCreateWorld={openCreationTypePicker}
onOpenCreateTypePicker={openCreationTypePicker}
onOpenGalleryDetail={openPublicGalleryDetail}
onOpenBabyLoveDrawing={startBabyLoveDrawingRuntime}
onOpenRecommendGalleryDetail={openRecommendGalleryDetail}
recommendRuntimeContent={recommendRuntimeContent}
activeRecommendEntryKey={activeRecommendEntryKey}
@@ -11196,8 +11450,11 @@ export function PlatformEntryFlowShellImpl({
onPublish={(draft) => {
void publishBabyObjectMatchResultDraft(draft);
}}
onRegenerateAssets={(draft) => {
void regenerateBabyObjectMatchResultAssets(draft);
}}
onStartTestRun={(draft) => {
startBabyObjectMatchRuntimeFromDraft(
void startBabyObjectMatchRuntimeFromDraft(
draft,
'baby-object-match-result',
);
@@ -11229,6 +11486,26 @@ export function PlatformEntryFlowShellImpl({
</motion.div>
)}
{selectionStage === 'baby-love-drawing-runtime' && (
<motion.div
key="baby-love-drawing-runtime"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 z-[100]"
>
<Suspense
fallback={<LazyPanelFallback label="正在加载宝贝爱画..." />}
>
<BabyLoveDrawingRuntimeShell
onBack={() => {
setSelectionStage('platform');
}}
/>
</Suspense>
</motion.div>
)}
{selectionStage === 'square-hole-agent-workspace' && (
<motion.div
key="square-hole-agent-workspace"
@@ -11938,6 +12215,56 @@ export function PlatformEntryFlowShellImpl({
</motion.div>
)}
{selectionStage === 'bark-battle-config' && (
<motion.div
key="bark-battle-config"
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -12 }}
className="flex h-full min-h-0 flex-col"
>
<Suspense
fallback={<LazyPanelFallback label="正在加载汪汪声浪配置..." />}
>
<BarkBattleConfigEditor
isBusy={isBarkBattleBusy}
onBack={leaveBarkBattleFlow}
onPublish={(payload) => {
void publishBarkBattleConfig(payload);
}}
/>
{barkBattleError ? (
<div className="platform-subpanel mx-auto mt-3 max-w-5xl rounded-2xl px-4 py-3 text-sm text-rose-200">
{barkBattleError}
</div>
) : null}
</Suspense>
</motion.div>
)}
{selectionStage === 'bark-battle-runtime' && barkBattlePublishedConfig && (
<motion.div
key="bark-battle-runtime"
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -12 }}
className="flex h-full min-h-0 flex-col"
>
<Suspense
fallback={<LazyPanelFallback label="正在加载汪汪声浪试玩..." />}
>
<BarkBattleRuntimeShell
title={barkBattlePublishedConfig.title}
workId={barkBattlePublishedConfig.workId}
publishedConfig={barkBattlePublishedConfig}
onExit={() => {
setSelectionStage('bark-battle-config');
}}
/>
</Suspense>
</motion.div>
)}
{selectionStage === 'custom-world-result' &&
sessionController.generatedCustomWorldProfile && (
<motion.div
@@ -12131,7 +12458,18 @@ export function PlatformEntryFlowShellImpl({
{creationEntryConfig ? (
<PlatformEntryCreationTypeModal
isOpen={showCreationTypeModal}
isBusy={sessionController.isCreatingAgentSession}
isBusy={
sessionController.isCreatingAgentSession ||
isCreativeAgentBusy ||
isCreativeAgentStreaming ||
isBigFishBusy ||
isMatch3DBusy ||
isSquareHoleBusy ||
isPuzzleBusy ||
(isVisualNovelCreationOpen && isVisualNovelBusy) ||
(isVisualNovelCreationOpen && isVisualNovelStreamingReply) ||
isBabyObjectMatchBusy
}
error={
creationEntryConfigError ??
bigFishError ??
@@ -12139,7 +12477,7 @@ export function PlatformEntryFlowShellImpl({
match3dError ??
squareHoleError ??
puzzleCreationError ??
visualNovelError ??
(isVisualNovelCreationOpen ? visualNovelError : null) ??
babyObjectMatchError ??
puzzleError ??
sessionController.creationTypeError
@@ -12147,7 +12485,18 @@ export function PlatformEntryFlowShellImpl({
entryConfig={creationEntryConfig}
creationTypes={creationEntryTypes}
onClose={() => {
if (sessionController.isCreatingAgentSession) {
if (
sessionController.isCreatingAgentSession ||
isCreativeAgentBusy ||
isCreativeAgentStreaming ||
isBigFishBusy ||
isMatch3DBusy ||
isSquareHoleBusy ||
isPuzzleBusy ||
(isVisualNovelCreationOpen && isVisualNovelBusy) ||
(isVisualNovelCreationOpen && isVisualNovelStreamingReply) ||
isBabyObjectMatchBusy
) {
return;
}
setShowCreationTypeModal(false);
@@ -12178,6 +12527,9 @@ export function PlatformEntryFlowShellImpl({
void openCreativeAgentWorkspace();
});
}}
onSelectBarkBattle={() => {
handleCreationHubCreateType('bark-battle');
}}
onSelectVisualNovel={() => {
handleCreationHubCreateType('visual-novel');
}}

View File

@@ -3,6 +3,7 @@ import { afterEach, expect, test, vi } from 'vitest';
import {
derivePlatformCreationTypes,
getVisiblePlatformCreationTypes,
isPlatformCreationTypeOpen,
isPlatformCreationTypeVisible,
} from './platformEntryCreationTypes';
@@ -109,6 +110,9 @@ test('visible platform creation types hide invisible cards and put locked cards
);
expect(isPlatformCreationTypeVisible(cards, 'hidden')).toBe(false);
expect(isPlatformCreationTypeVisible(cards, 'open')).toBe(true);
expect(isPlatformCreationTypeOpen(cards, 'hidden')).toBe(false);
expect(isPlatformCreationTypeOpen(cards, 'locked')).toBe(false);
expect(isPlatformCreationTypeOpen(cards, 'open')).toBe(true);
expect(
cards.every((item) =>
item.imageSrc.startsWith('/creation-type-references/'),
@@ -123,7 +127,7 @@ test('edutainment switch hides baby object match creation entry from database co
title: '宝贝识物',
subtitle: '亲子识物分类',
badge: '可创建',
imageSrc: '/creation-type-references/baby-object-match.webp',
imageSrc: '/child-motion-demo/picture-book-grass-stage.png',
visible: true,
open: true,
sortOrder: 1,
@@ -152,7 +156,7 @@ test('edutainment switch hides baby object match creation entry from database co
title: '宝贝识物',
subtitle: '亲子识物分类',
badge: '可创建',
imageSrc: '/creation-type-references/baby-object-match.webp',
imageSrc: '/child-motion-demo/picture-book-grass-stage.png',
visible: true,
open: true,
sortOrder: 1,

View File

@@ -32,6 +32,15 @@ export function isPlatformCreationTypeVisible(
return creationTypes.some((item) => item.id === id && !item.hidden);
}
export function isPlatformCreationTypeOpen(
creationTypes: readonly PlatformCreationTypeCard[],
id: PlatformCreationTypeId,
) {
return creationTypes.some(
(item) => item.id === id && !item.hidden && !item.locked,
);
}
/**
* 创作入口卡片只做展示派生;配置事实源来自后端 API / SpacetimeDB前端不再保留入口默认配置。
*/

View File

@@ -31,6 +31,8 @@ export type SelectionStage =
| 'square-hole-generating'
| 'square-hole-result'
| 'square-hole-runtime'
| 'bark-battle-config'
| 'bark-battle-runtime'
| 'creative-agent-workspace'
| 'visual-novel-agent-workspace'
| 'visual-novel-generating'
@@ -41,6 +43,7 @@ export type SelectionStage =
| 'baby-object-match-generating'
| 'baby-object-match-result'
| 'baby-object-match-runtime'
| 'baby-love-drawing-runtime'
| 'puzzle-agent-workspace'
| 'puzzle-generating'
| 'puzzle-onboarding'