fix: show published big fish works in gallery
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-27 00:09:09 +08:00
parent 44b08dd51a
commit 615d828add
19 changed files with 663 additions and 114 deletions

View File

@@ -48,6 +48,7 @@ import {
startBigFishRuntimeRun,
submitBigFishRuntimeInput,
} from '../../services/big-fish-runtime';
import { listBigFishGallery } from '../../services/big-fish-gallery';
import {
deleteBigFishWork,
listBigFishWorks,
@@ -64,7 +65,10 @@ import {
type MiniGameDraftGenerationState,
} from '../../services/miniGameDraftGenerationProgress';
import { getPlatformProfileDashboard } from '../../services/platform-entry/platformProfileClient';
import { isSamePuzzlePublicWorkCode } from '../../services/publicWorkCode';
import {
isSameBigFishPublicWorkCode,
isSamePuzzlePublicWorkCode,
} from '../../services/publicWorkCode';
import {
createPuzzleAgentSession,
executePuzzleAgentAction,
@@ -91,7 +95,9 @@ import {
import type { CustomWorldProfile } from '../../types';
import { useAuthUi } from '../auth/AuthUiContext';
import {
isBigFishGalleryEntry,
isPuzzleGalleryEntry,
mapBigFishWorkToPlatformGalleryCard,
mapPuzzleWorkToPlatformGalleryCard,
type PlatformPublicGalleryCard,
} from '../rpg-entry/rpgEntryWorldPresentation';
@@ -146,7 +152,12 @@ function getPlatformPublicGalleryEntryTime(entry: PlatformPublicGalleryCard) {
}
function getPlatformPublicGalleryEntryKey(entry: PlatformPublicGalleryCard) {
return `${isPuzzleGalleryEntry(entry) ? 'puzzle' : 'rpg'}:${entry.ownerUserId}:${entry.profileId}`;
const kind = isBigFishGalleryEntry(entry)
? 'big-fish'
: isPuzzleGalleryEntry(entry)
? 'puzzle'
: 'rpg';
return `${kind}:${entry.ownerUserId}:${entry.profileId}`;
}
function mergePlatformPublicGalleryEntries(
@@ -393,6 +404,9 @@ export function PlatformEntryFlowShellImpl({
const [selectedDetailEntry, setSelectedDetailEntry] =
useState<CustomWorldLibraryEntry<CustomWorldProfile> | null>(null);
const [bigFishWorks, setBigFishWorks] = useState<BigFishWorkSummary[]>([]);
const [bigFishGalleryEntries, setBigFishGalleryEntries] = useState<
BigFishWorkSummary[]
>([]);
const [bigFishRun, setBigFishRun] =
useState<BigFishRuntimeSnapshotResponse | null>(null);
const [isBigFishLoadingLibrary, setIsBigFishLoadingLibrary] = useState(false);
@@ -460,6 +474,64 @@ export function PlatformEntryFlowShellImpl({
[],
);
const refreshBigFishShelf = useCallback(async () => {
setIsBigFishLoadingLibrary(true);
try {
const worksResponse = await listBigFishWorks();
setBigFishWorks(worksResponse.items);
setBigFishError(null);
} catch (error) {
setBigFishError(
resolveBigFishErrorMessage(error, '读取大鱼吃小鱼作品列表失败。'),
);
} finally {
setIsBigFishLoadingLibrary(false);
}
}, [resolveBigFishErrorMessage]);
const refreshBigFishGallery = useCallback(async () => {
try {
const galleryResponse = await listBigFishGallery();
setBigFishGalleryEntries(galleryResponse.items);
return galleryResponse.items;
} catch (error) {
setBigFishGalleryEntries([]);
setBigFishError(
resolveBigFishErrorMessage(error, '读取大鱼吃小鱼广场失败。'),
);
return [];
}
}, [resolveBigFishErrorMessage]);
const refreshPuzzleShelf = useCallback(async () => {
setIsPuzzleLoadingLibrary(true);
try {
const worksResponse = await listPuzzleWorks();
setPuzzleWorks(worksResponse.items);
setPuzzleError(null);
} catch (error) {
setPuzzleError(
resolvePuzzleErrorMessage(error, '读取拼图作品列表失败。'),
);
} finally {
setIsPuzzleLoadingLibrary(false);
}
}, [resolvePuzzleErrorMessage]);
const refreshPuzzleGallery = useCallback(async () => {
try {
const galleryResponse = await listPuzzleGallery();
setPuzzleGalleryEntries(galleryResponse.items);
return galleryResponse.items;
} catch (error) {
setPuzzleGalleryEntries([]);
setPuzzleError(resolvePuzzleErrorMessage(error, '读取拼图广场失败。'));
return [];
}
}, [resolvePuzzleErrorMessage]);
const sessionController = useRpgCreationSessionController({
userId: authUi?.user?.id,
openLoginModal: authUi?.openLoginModal,
@@ -552,6 +624,7 @@ export function PlatformEntryFlowShellImpl({
await Promise.allSettled([
platformBootstrap.refreshPublishedGallery(),
platformBootstrap.refreshCustomWorldWorks(),
refreshBigFishGallery(),
refreshPuzzleGallery(),
]);
return latestSession;
@@ -606,21 +679,35 @@ export function PlatformEntryFlowShellImpl({
}, [agentResultPreview]);
const featuredGalleryEntries = useMemo(() => {
const bigFishPublicEntries = bigFishGalleryEntries.map(
mapBigFishWorkToPlatformGalleryCard,
);
const puzzlePublicEntries = puzzleGalleryEntries.map(
mapPuzzleWorkToPlatformGalleryCard,
);
return mergePlatformPublicGalleryEntries(
platformBootstrap.publishedGalleryEntries,
puzzlePublicEntries,
[...bigFishPublicEntries, ...puzzlePublicEntries],
).slice(0, 6);
}, [platformBootstrap.publishedGalleryEntries, puzzleGalleryEntries]);
}, [
bigFishGalleryEntries,
platformBootstrap.publishedGalleryEntries,
puzzleGalleryEntries,
]);
const latestGalleryEntries = useMemo(
() =>
mergePlatformPublicGalleryEntries(
platformBootstrap.publishedGalleryEntries,
puzzleGalleryEntries.map(mapPuzzleWorkToPlatformGalleryCard),
[
...bigFishGalleryEntries.map(mapBigFishWorkToPlatformGalleryCard),
...puzzleGalleryEntries.map(mapPuzzleWorkToPlatformGalleryCard),
],
),
[platformBootstrap.publishedGalleryEntries, puzzleGalleryEntries],
[
bigFishGalleryEntries,
platformBootstrap.publishedGalleryEntries,
puzzleGalleryEntries,
],
);
const creationHubItems =
@@ -680,50 +767,6 @@ export function PlatformEntryFlowShellImpl({
setShowCreationTypeModal(true);
}, [prepareCreationLaunch]);
const refreshBigFishShelf = useCallback(async () => {
setIsBigFishLoadingLibrary(true);
try {
const worksResponse = await listBigFishWorks();
setBigFishWorks(worksResponse.items);
setBigFishError(null);
} catch (error) {
setBigFishError(
resolveBigFishErrorMessage(error, '读取大鱼吃小鱼作品列表失败。'),
);
} finally {
setIsBigFishLoadingLibrary(false);
}
}, [resolveBigFishErrorMessage]);
const refreshPuzzleShelf = useCallback(async () => {
setIsPuzzleLoadingLibrary(true);
try {
const worksResponse = await listPuzzleWorks();
setPuzzleWorks(worksResponse.items);
setPuzzleError(null);
} catch (error) {
setPuzzleError(
resolvePuzzleErrorMessage(error, '读取拼图作品列表失败。'),
);
} finally {
setIsPuzzleLoadingLibrary(false);
}
}, [resolvePuzzleErrorMessage]);
const refreshPuzzleGallery = useCallback(async () => {
try {
const galleryResponse = await listPuzzleGallery();
setPuzzleGalleryEntries(galleryResponse.items);
return galleryResponse.items;
} catch (error) {
setPuzzleGalleryEntries([]);
setPuzzleError(resolvePuzzleErrorMessage(error, '读取拼图广场失败。'));
return [];
}
}, [resolvePuzzleErrorMessage]);
const bigFishFlow = usePlatformCreationAgentFlowController<
BigFishSessionSnapshotResponse,
Record<string, never>,
@@ -761,6 +804,7 @@ export function PlatformEntryFlowShellImpl({
setSession(response.session);
if (payload.action === 'big_fish_publish_game') {
void refreshBigFishShelf();
void refreshBigFishGallery();
}
if (payload.action !== 'big_fish_compile_draft') {
return;
@@ -1081,6 +1125,34 @@ export function PlatformEntryFlowShellImpl({
setSelectionStage,
]);
const restartBigFishRun = useCallback(async () => {
const sessionId = bigFishSession?.sessionId ?? bigFishRun?.sessionId;
if (!sessionId || isBigFishBusy) {
return;
}
setIsBigFishBusy(true);
setBigFishError(null);
try {
const { run } = await startBigFishRuntimeRun(sessionId);
setBigFishRun(run);
setSelectionStage('big-fish-runtime');
} catch (error) {
setBigFishError(
resolveBigFishErrorMessage(error, '重新开始大鱼吃小鱼玩法失败。'),
);
} finally {
setIsBigFishBusy(false);
}
}, [
bigFishRun?.sessionId,
bigFishSession?.sessionId,
isBigFishBusy,
resolveBigFishErrorMessage,
setSelectionStage,
]);
const startPuzzleRunFromProfile = useCallback(
async (profileId: string) => {
if (isPuzzleBusy) {
@@ -1390,8 +1462,9 @@ export function PlatformEntryFlowShellImpl({
setBigFishError(null);
void deleteBigFishWork(work.sourceSessionId)
.then((response) => {
.then(async (response) => {
setBigFishWorks(response.items);
await refreshBigFishGallery().catch(() => []);
})
.catch((error) => {
setBigFishError(
@@ -1403,7 +1476,12 @@ export function PlatformEntryFlowShellImpl({
});
});
},
[deletingCreationWorkId, resolveBigFishErrorMessage, runProtectedAction],
[
deletingCreationWorkId,
refreshBigFishGallery,
resolveBigFishErrorMessage,
runProtectedAction,
],
);
const handleDeletePuzzleWork = useCallback(
@@ -1499,6 +1577,33 @@ export function PlatformEntryFlowShellImpl({
[openPuzzleDetail, puzzleFlow, refreshPuzzleShelf, setPuzzleError],
);
const startBigFishRunFromWork = useCallback(
async (item: BigFishWorkSummary) => {
const sessionId = item.sourceSessionId?.trim();
if (!sessionId) {
setBigFishError('当前作品缺少会话信息,暂时无法进入玩法。');
return;
}
setIsBigFishBusy(true);
setBigFishError(null);
try {
const { run } = await startBigFishRuntimeRun(sessionId);
bigFishFlow.setSession(null);
setBigFishRun(run);
setSelectionStage('big-fish-runtime');
} catch (error) {
setBigFishError(
resolveBigFishErrorMessage(error, '启动大鱼吃小鱼玩法失败。'),
);
} finally {
setIsBigFishBusy(false);
}
},
[bigFishFlow, resolveBigFishErrorMessage, setSelectionStage],
);
const handlePublicCodeSearch = useCallback(
async (keyword: string) => {
const normalizedKeyword = keyword.trim();
@@ -1514,15 +1619,19 @@ export function PlatformEntryFlowShellImpl({
const shouldSearchUserIdFirst = /^user[_-][a-z0-9_-]+$/iu.test(
normalizedKeyword,
);
const shouldSearchBigFishFirst = upperKeyword.startsWith('BF');
const shouldSearchPuzzleFirst = upperKeyword.startsWith('PZ');
const shouldSearchWorkFirst =
!shouldSearchUserIdFirst &&
!shouldSearchBigFishFirst &&
!shouldSearchPuzzleFirst &&
(upperKeyword.startsWith('CW') || /^\d{1,8}$/u.test(normalizedKeyword));
const shouldSearchUserFirst =
shouldSearchUserIdFirst ||
upperKeyword.startsWith('SY') ||
(!shouldSearchWorkFirst && !shouldSearchPuzzleFirst);
(!shouldSearchWorkFirst &&
!shouldSearchBigFishFirst &&
!shouldSearchPuzzleFirst);
const tryOpenGalleryEntry = async () => {
const entry =
@@ -1562,6 +1671,21 @@ export function PlatformEntryFlowShellImpl({
tab: platformBootstrap.platformTab,
});
};
const tryOpenBigFishGalleryEntry = async () => {
const entries =
bigFishGalleryEntries.length > 0
? bigFishGalleryEntries
: await refreshBigFishGallery();
const matchedEntry = entries.find((entry) =>
isSameBigFishPublicWorkCode(normalizedKeyword, entry.sourceSessionId),
);
if (!matchedEntry) {
throw new Error('未找到大鱼吃小鱼作品。');
}
await startBigFishRunFromWork(matchedEntry);
};
try {
if (shouldSearchUserIdFirst) {
@@ -1575,6 +1699,11 @@ export function PlatformEntryFlowShellImpl({
return;
}
if (shouldSearchBigFishFirst) {
await tryOpenBigFishGalleryEntry();
return;
}
if (shouldSearchWorkFirst) {
try {
await tryOpenGalleryEntry();
@@ -1611,10 +1740,13 @@ export function PlatformEntryFlowShellImpl({
},
[
detailNavigation,
bigFishGalleryEntries,
openPuzzleDetail,
platformBootstrap.platformTab,
puzzleGalleryEntries,
refreshBigFishGallery,
refreshPuzzleGallery,
startBigFishRunFromWork,
],
);
@@ -1631,39 +1763,12 @@ export function PlatformEntryFlowShellImpl({
[bigFishFlow, refreshBigFishShelf],
);
const startBigFishRunFromWork = useCallback(
async (item: BigFishWorkSummary) => {
const sessionId = item.sourceSessionId?.trim();
if (!sessionId) {
setBigFishError('当前作品缺少会话信息,暂时无法进入玩法。');
return;
}
setIsBigFishBusy(true);
setBigFishError(null);
try {
const { session } = await getBigFishCreationSession(sessionId);
const { run } = await startBigFishRuntimeRun(sessionId);
bigFishFlow.setSession(session);
setBigFishRun(run);
setSelectionStage('big-fish-runtime');
} catch (error) {
setBigFishError(
resolveBigFishErrorMessage(error, '启动大鱼吃小鱼玩法失败。'),
);
} finally {
setIsBigFishBusy(false);
}
},
[bigFishFlow, resolveBigFishErrorMessage, setSelectionStage],
);
useEffect(() => {
if (selectionStage === 'platform') {
void refreshBigFishGallery();
void refreshPuzzleGallery();
}
}, [refreshPuzzleGallery, selectionStage]);
}, [refreshBigFishGallery, refreshPuzzleGallery, selectionStage]);
useEffect(() => {
if (
@@ -1836,6 +1941,33 @@ export function PlatformEntryFlowShellImpl({
onOpenCreateWorld={openCreationTypePicker}
onOpenCreateTypePicker={openCreationTypePicker}
onOpenGalleryDetail={(entry) => {
if (isBigFishGalleryEntry(entry)) {
runProtectedAction(() => {
void startBigFishRunFromWork({
workId: entry.workId,
sourceSessionId: entry.profileId,
ownerUserId: entry.ownerUserId,
title: entry.worldName,
subtitle: entry.subtitle,
summary: entry.summaryText,
coverImageSrc: entry.coverImageSrc,
status: 'published',
updatedAt: entry.updatedAt,
publishReady: true,
levelCount: Number.parseInt(
entry.themeTags
.find((tag) => /^\d+$/u.test(tag))
?.replace('级', '') ?? '0',
10,
),
levelMainImageReadyCount: 0,
levelMotionReadyCount: 0,
backgroundReady: Boolean(entry.coverImageSrc),
});
});
return;
}
if (isPuzzleGalleryEntry(entry)) {
void openPuzzleDetail(entry.profileId, {
tab: platformBootstrap.platformTab,
@@ -2109,10 +2241,12 @@ export function PlatformEntryFlowShellImpl({
isBusy={isBigFishBusy}
error={bigFishError}
onBack={() => {
setSelectionStage('big-fish-result');
setSelectionStage(
bigFishSession ? 'big-fish-result' : 'platform',
);
}}
onRestart={() => {
void startBigFishRun();
void restartBigFishRun();
}}
onSubmitInput={submitBigFishInput}
/>
@@ -2128,7 +2262,9 @@ export function PlatformEntryFlowShellImpl({
exit={{ opacity: 0, y: -12 }}
className="flex h-full min-h-0 flex-col"
>
<Suspense fallback={<LazyPanelFallback label="正在加载拼图创作..." />}>
<Suspense
fallback={<LazyPanelFallback label="正在加载拼图创作..." />}
>
<PuzzleAgentWorkspace
session={puzzleSession}
activeOperation={puzzleOperation}
@@ -2201,7 +2337,9 @@ export function PlatformEntryFlowShellImpl({
exit={{ opacity: 0, y: -12 }}
className="flex h-full min-h-0 flex-col"
>
<Suspense fallback={<LazyPanelFallback label="正在加载拼图结果..." />}>
<Suspense
fallback={<LazyPanelFallback label="正在加载拼图结果..." />}
>
<PuzzleResultView
session={puzzleSession}
author={authUi?.user ?? null}
@@ -2226,7 +2364,9 @@ export function PlatformEntryFlowShellImpl({
exit={{ opacity: 0, y: -12 }}
className="flex h-full min-h-0 flex-col"
>
<Suspense fallback={<LazyPanelFallback label="正在加载拼图详情..." />}>
<Suspense
fallback={<LazyPanelFallback label="正在加载拼图详情..." />}
>
<PuzzleGalleryDetailView
item={selectedPuzzleDetail}
isBusy={isPuzzleBusy}
@@ -2249,7 +2389,9 @@ export function PlatformEntryFlowShellImpl({
: null
}
onStartGame={() => {
void startPuzzleRunFromProfile(selectedPuzzleDetail.profileId);
void startPuzzleRunFromProfile(
selectedPuzzleDetail.profileId,
);
}}
/>
</Suspense>
@@ -2264,7 +2406,9 @@ export function PlatformEntryFlowShellImpl({
exit={{ opacity: 0 }}
className="fixed inset-0 z-[100]"
>
<Suspense fallback={<LazyPanelFallback label="正在加载拼图玩法..." />}>
<Suspense
fallback={<LazyPanelFallback label="正在加载拼图玩法..." />}
>
<PuzzleRuntimeShell
run={puzzleRun}
isBusy={isPuzzleBusy || isPuzzleNextLevelGenerating}