feat: add edutainment drawing and visual package flows

This commit is contained in:
2026-05-14 14:17:10 +08:00
parent 10e8beea80
commit e444266e1e
109 changed files with 8788 additions and 996 deletions

View File

@@ -150,8 +150,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 +326,7 @@ import type { PlatformCreationTypeId } from './platformEntryCreationTypes';
import {
derivePlatformCreationTypes,
getVisiblePlatformCreationTypes,
isPlatformCreationTypeOpen,
isPlatformCreationTypeVisible,
} from './platformEntryCreationTypes';
import {
@@ -1963,6 +1967,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 {
@@ -2301,6 +2314,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<
@@ -2718,6 +2735,12 @@ export function PlatformEntryFlowShellImpl({
}, [resolvePuzzleErrorMessage]);
const refreshVisualNovelShelf = useCallback(async () => {
if (!isVisualNovelCreationOpen) {
setVisualNovelWorks([]);
visualNovelErrorSetterRef.current(null);
return [];
}
setIsVisualNovelLoadingLibrary(true);
try {
@@ -2733,9 +2756,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);
@@ -2747,7 +2776,7 @@ export function PlatformEntryFlowShellImpl({
);
return [];
}
}, [resolvePuzzleErrorMessage]);
}, [isVisualNovelCreationOpen, resolvePuzzleErrorMessage]);
const handleRpgDraftGenerationStarted = useCallback(
(sessionId: string) => {
@@ -2775,8 +2804,8 @@ export function PlatformEntryFlowShellImpl({
[markDraftReady, platformBootstrap],
);
const refreshBabyObjectMatchShelf = useCallback(() => {
setBabyObjectMatchDrafts(listLocalBabyObjectMatchDrafts());
const refreshBabyObjectMatchShelf = useCallback(async () => {
setBabyObjectMatchDrafts(await listLocalBabyObjectMatchDrafts());
}, []);
const sessionController = useRpgCreationSessionController({
@@ -3004,7 +3033,7 @@ export function PlatformEntryFlowShellImpl({
...match3dPublicEntries,
...puzzlePublicEntries,
...squareHolePublicEntries,
...visualNovelPublicEntries,
...(isVisualNovelCreationOpen ? visualNovelPublicEntries : []),
...babyObjectMatchPublicEntries,
],
).slice(0, 6);
@@ -3012,6 +3041,7 @@ export function PlatformEntryFlowShellImpl({
babyObjectMatchDrafts,
isBigFishCreationVisible,
isBabyObjectMatchVisible,
isVisualNovelCreationOpen,
bigFishGalleryEntries,
match3dGalleryEntries,
platformBootstrap.publishedGalleryEntries,
@@ -3032,9 +3062,11 @@ export function PlatformEntryFlowShellImpl({
...squareHoleGalleryEntries.map(
mapSquareHoleWorkToPlatformGalleryCard,
),
...visualNovelGalleryEntries.map(
mapVisualNovelWorkToPlatformGalleryCard,
),
...(isVisualNovelCreationOpen
? visualNovelGalleryEntries.map(
mapVisualNovelWorkToPlatformGalleryCard,
)
: []),
...(isBabyObjectMatchVisible
? babyObjectMatchDrafts
.filter((draft) => draft.publicationStatus === 'published')
@@ -3046,6 +3078,7 @@ export function PlatformEntryFlowShellImpl({
babyObjectMatchDrafts,
isBabyObjectMatchVisible,
isBigFishCreationVisible,
isVisualNovelCreationOpen,
bigFishGalleryEntries,
match3dGalleryEntries,
platformBootstrap.publishedGalleryEntries,
@@ -3112,14 +3145,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) => {
@@ -4886,7 +4922,7 @@ export function PlatformEntryFlowShellImpl({
try {
const response = await createBabyObjectMatchDraft(payload);
setBabyObjectMatchDraft(response.draft);
refreshBabyObjectMatchShelf();
void refreshBabyObjectMatchShelf();
setBabyObjectMatchGenerationPhase('ready');
setBabyObjectMatchGenerationState((current) =>
current
@@ -5239,7 +5275,7 @@ export function PlatformEntryFlowShellImpl({
try {
const response = await saveBabyObjectMatchDraft({ draft });
setBabyObjectMatchDraft(response.draft);
refreshBabyObjectMatchShelf();
void refreshBabyObjectMatchShelf();
} catch (error) {
setBabyObjectMatchError(
resolvePuzzleErrorMessage(error, '保存宝贝识物草稿失败。'),
@@ -5251,14 +5287,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:
@@ -5268,13 +5342,17 @@ export function PlatformEntryFlowShellImpl({
});
} catch (error) {
setBabyObjectMatchError(
resolvePuzzleErrorMessage(error, '发布宝贝识物作品失败。'),
resolvePuzzleErrorMessage(
error,
'生成宝贝识物 image-2 资源失败,请重试后再发布。',
),
);
} finally {
setIsBabyObjectMatchBusy(false);
}
},
[
ensureBabyObjectMatchGeneratedAssets,
openPublishShareModal,
refreshBabyObjectMatchShelf,
resolvePuzzleErrorMessage,
@@ -5282,40 +5360,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;
}
@@ -5324,7 +5428,7 @@ export function PlatformEntryFlowShellImpl({
babyObjectMatchDrafts.find(
(draft) => draft.profileId === entry.profileId,
) ??
listLocalBabyObjectMatchDrafts().find(
(await listLocalBabyObjectMatchDrafts()).find(
(draft) => draft.profileId === entry.profileId,
) ??
null
@@ -5334,12 +5438,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(
'当前宝贝识物作品缺少本地草稿,暂时无法进入玩法。',
@@ -5347,7 +5451,11 @@ export function PlatformEntryFlowShellImpl({
return false;
}
return startBabyObjectMatchRuntimeFromDraft(draft, returnStage, options);
return await startBabyObjectMatchRuntimeFromDraft(
draft,
returnStage,
options,
);
},
[resolveBabyObjectMatchRuntimeDraft, startBabyObjectMatchRuntimeFromDraft],
);
@@ -6968,7 +7076,7 @@ export function PlatformEntryFlowShellImpl({
return;
}
runProtectedAction(() => {
runProtectedAction(async () => {
setIsPublicWorkDetailBusy(true);
setIsPuzzleBusy(true);
setPuzzleError(null);
@@ -7447,6 +7555,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);
@@ -8641,7 +8810,7 @@ export function PlatformEntryFlowShellImpl({
if (isEdutainmentGalleryEntry(selectedPublicWorkDetail)) {
setPublicWorkDetailError(null);
startBabyObjectMatchRuntimeFromEntry(
void startBabyObjectMatchRuntimeFromEntry(
selectedPublicWorkDetail,
'work-detail',
);
@@ -8773,9 +8942,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;
}
@@ -9328,7 +9501,7 @@ export function PlatformEntryFlowShellImpl({
return;
}
runProtectedAction(() => {
runProtectedAction(async () => {
setPublicWorkDetailError(null);
// 中文注释:自有公开作品必须恢复原草稿,不能复用 remix 复制链路。
@@ -9396,7 +9569,7 @@ export function PlatformEntryFlowShellImpl({
}
if (isEdutainmentGalleryEntry(entry)) {
const matchedDraft = resolveBabyObjectMatchRuntimeDraft(entry);
const matchedDraft = await resolveBabyObjectMatchRuntimeDraft(entry);
if (!matchedDraft) {
setPublicWorkDetailError('这份宝贝识物缺少可编辑草稿。');
return;
@@ -9623,8 +9796,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) => {
@@ -9667,7 +9840,7 @@ export function PlatformEntryFlowShellImpl({
}
if (shouldSearchBabyObjectFirst) {
tryOpenBabyObjectMatchGalleryEntry();
await tryOpenBabyObjectMatchGalleryEntry();
return;
}
@@ -9927,11 +10100,14 @@ export function PlatformEntryFlowShellImpl({
if (isSquareHoleCreationVisible) {
void refreshSquareHoleGallery();
}
void refreshVisualNovelGallery();
if (isVisualNovelCreationOpen) {
void refreshVisualNovelGallery();
}
}
}, [
isBigFishCreationVisible,
isSquareHoleCreationVisible,
isVisualNovelCreationOpen,
refreshBigFishGallery,
refreshMatch3DGallery,
refreshPuzzleGallery,
@@ -9951,11 +10127,14 @@ export function PlatformEntryFlowShellImpl({
if (isSquareHoleCreationVisible) {
void refreshSquareHoleShelf();
}
void refreshVisualNovelShelf();
refreshBabyObjectMatchShelf();
if (isVisualNovelCreationOpen) {
void refreshVisualNovelShelf();
}
void refreshBabyObjectMatchShelf();
}
}, [
isSquareHoleCreationVisible,
isVisualNovelCreationOpen,
platformBootstrap.canReadProtectedData,
platformBootstrap.platformTab,
refreshBabyObjectMatchShelf,
@@ -9998,7 +10177,7 @@ export function PlatformEntryFlowShellImpl({
isMatch3DLoadingLibrary ||
(isSquareHoleCreationVisible && isSquareHoleLoadingLibrary) ||
isPuzzleLoadingLibrary ||
isVisualNovelLoadingLibrary ||
(isVisualNovelCreationOpen && isVisualNovelLoadingLibrary) ||
isBabyObjectMatchBusy
}
error={
@@ -10007,7 +10186,7 @@ export function PlatformEntryFlowShellImpl({
isMatch3DLoadingLibrary ||
(isSquareHoleCreationVisible && isSquareHoleLoadingLibrary) ||
isPuzzleLoadingLibrary ||
isVisualNovelLoadingLibrary ||
(isVisualNovelCreationOpen && isVisualNovelLoadingLibrary) ||
isBabyObjectMatchBusy
? null
: (platformBootstrap.platformError ??
@@ -10017,7 +10196,7 @@ export function PlatformEntryFlowShellImpl({
(isSquareHoleCreationVisible ? squareHoleError : null) ??
puzzleShelfError ??
puzzleError ??
visualNovelError ??
(isVisualNovelCreationOpen ? visualNovelError : null) ??
babyObjectMatchError)
}
onRetry={() => {
@@ -10053,8 +10232,10 @@ export function PlatformEntryFlowShellImpl({
void refreshSquareHoleShelf();
}
void refreshPuzzleShelf();
void refreshVisualNovelShelf();
refreshBabyObjectMatchShelf();
if (isVisualNovelCreationOpen) {
void refreshVisualNovelShelf();
}
void refreshBabyObjectMatchShelf();
}}
createError={
creationEntryConfigError ??
@@ -10064,7 +10245,7 @@ export function PlatformEntryFlowShellImpl({
(isSquareHoleCreationVisible ? squareHoleError : null) ??
puzzleCreationError ??
puzzleError ??
visualNovelError ??
(isVisualNovelCreationOpen ? visualNovelError : null) ??
babyObjectMatchError
}
createBusy={
@@ -10076,8 +10257,8 @@ export function PlatformEntryFlowShellImpl({
isMatch3DBusy ||
(isSquareHoleCreationVisible && isSquareHoleBusy) ||
isPuzzleBusy ||
isVisualNovelBusy ||
isVisualNovelStreamingReply ||
(isVisualNovelCreationOpen && isVisualNovelBusy) ||
(isVisualNovelCreationOpen && isVisualNovelStreamingReply) ||
isBabyObjectMatchBusy
}
entryConfig={creationEntryConfig}
@@ -10174,6 +10355,9 @@ export function PlatformEntryFlowShellImpl({
openBabyObjectMatchDraft(item);
});
}}
onDeleteBabyObjectMatch={(item) => {
handleDeleteBabyObjectMatchWork(item);
}}
visualNovelItems={visualNovelShelfItems}
onOpenVisualNovelDetail={(item) => {
runProtectedAction(() => {
@@ -10408,6 +10592,7 @@ export function PlatformEntryFlowShellImpl({
onOpenCreateWorld={openCreationTypePicker}
onOpenCreateTypePicker={openCreationTypePicker}
onOpenGalleryDetail={openPublicGalleryDetail}
onOpenBabyLoveDrawing={startBabyLoveDrawingRuntime}
onOpenRecommendGalleryDetail={openRecommendGalleryDetail}
recommendRuntimeContent={recommendRuntimeContent}
activeRecommendEntryKey={activeRecommendEntryKey}
@@ -11159,8 +11344,11 @@ export function PlatformEntryFlowShellImpl({
onPublish={(draft) => {
void publishBabyObjectMatchResultDraft(draft);
}}
onRegenerateAssets={(draft) => {
void regenerateBabyObjectMatchResultAssets(draft);
}}
onStartTestRun={(draft) => {
startBabyObjectMatchRuntimeFromDraft(
void startBabyObjectMatchRuntimeFromDraft(
draft,
'baby-object-match-result',
);
@@ -11192,6 +11380,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"
@@ -12094,7 +12302,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 ??
@@ -12102,7 +12321,7 @@ export function PlatformEntryFlowShellImpl({
match3dError ??
squareHoleError ??
puzzleCreationError ??
visualNovelError ??
(isVisualNovelCreationOpen ? visualNovelError : null) ??
babyObjectMatchError ??
puzzleError ??
sessionController.creationTypeError
@@ -12110,7 +12329,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);

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

@@ -41,6 +41,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'