This commit is contained in:
2026-05-13 00:28:07 +08:00
parent ef4f91a75e
commit 01c5ab985a
101 changed files with 10635 additions and 2292 deletions

View File

@@ -161,6 +161,7 @@ import {
listMatch3DGallery,
listMatch3DWorks,
} from '../../services/match3d-works';
import { preloadMatch3DGeneratedModelAssets } from '../../services/match3dGeneratedModelCache';
import {
buildBigFishGenerationAnchorEntries,
buildMatch3DGenerationAnchorEntries,
@@ -602,6 +603,13 @@ function mapPublicWorkDetailToMatch3DWork(
updatedAt: entry.updatedAt,
publishedAt: entry.publishedAt,
publishReady: true,
backgroundPrompt: entry.backgroundPrompt ?? null,
backgroundImageSrc: entry.backgroundImageSrc ?? null,
backgroundImageObjectKey: entry.backgroundImageObjectKey ?? null,
generatedBackgroundAsset:
entry.generatedItemAssets
?.map((asset) => asset.backgroundAsset ?? null)
.find(Boolean) ?? null,
generatedItemAssets: entry.generatedItemAssets ?? [],
};
}
@@ -633,10 +641,24 @@ function buildMatch3DProfileFromSession(
updatedAt: now,
publishedAt: null,
publishReady: Boolean(draft.publishReady),
backgroundPrompt: draft.backgroundPrompt ?? null,
backgroundImageSrc: draft.backgroundImageSrc ?? null,
backgroundImageObjectKey: draft.backgroundImageObjectKey ?? null,
generatedBackgroundAsset: draft.generatedBackgroundAsset ?? null,
generatedItemAssets: draft.generatedItemAssets,
};
}
function hasMatch3DGeneratedModelAsset(
assets: readonly Match3DGeneratedItemAsset[] | null | undefined,
) {
return Boolean(
assets?.some(
(asset) => asset.modelSrc?.trim() || asset.modelObjectKey?.trim(),
),
);
}
function resolveMatch3DRuntimeGeneratedItemAssets(
run: Match3DRunSnapshot | null,
profile: Match3DWorkProfile | null,
@@ -650,7 +672,7 @@ function resolveMatch3DRuntimeGeneratedItemAssets(
: [];
if (runProfileId && profile?.profileId === runProfileId) {
if (profileAssets.length > 0) {
if (hasMatch3DGeneratedModelAsset(profileAssets)) {
return profileAssets;
}
@@ -659,7 +681,9 @@ function resolveMatch3DRuntimeGeneratedItemAssets(
isMatch3DGalleryEntry(publicWorkDetail) &&
publicWorkDetail.profileId === runProfileId
) {
return publicDetailAssets;
return hasMatch3DGeneratedModelAsset(publicDetailAssets)
? publicDetailAssets
: profileAssets;
}
}
@@ -672,7 +696,57 @@ function resolveMatch3DRuntimeGeneratedItemAssets(
return publicDetailAssets;
}
return profileAssets.length > 0 ? profileAssets : publicDetailAssets;
return hasMatch3DGeneratedModelAsset(profileAssets)
? profileAssets
: publicDetailAssets;
}
function resolveActiveMatch3DRuntimeProfile(
run: Match3DRunSnapshot | null,
runtimeProfile: Match3DWorkProfile | null,
profile: Match3DWorkProfile | null,
) {
const runProfileId = run?.profileId?.trim() ?? '';
if (runProfileId && runtimeProfile?.profileId === runProfileId) {
return runtimeProfile;
}
if (runProfileId && profile?.profileId === runProfileId) {
return profile;
}
return runtimeProfile ?? profile;
}
function resolveMatch3DRuntimeBackgroundImageSrc(
run: Match3DRunSnapshot | null,
profile: Match3DWorkProfile | null,
publicWorkDetail: PlatformPublicGalleryCard | null,
) {
const runProfileId = run?.profileId?.trim() ?? '';
const profileBackground =
profile?.backgroundImageSrc?.trim() ||
profile?.generatedBackgroundAsset?.imageSrc?.trim() ||
profile?.backgroundImageObjectKey?.trim() ||
profile?.generatedBackgroundAsset?.imageObjectKey?.trim() ||
'';
const publicBackground =
publicWorkDetail && isMatch3DGalleryEntry(publicWorkDetail)
? publicWorkDetail.backgroundImageSrc?.trim() ||
publicWorkDetail.backgroundImageObjectKey?.trim() ||
''
: '';
if (runProfileId && profile?.profileId === runProfileId) {
return profileBackground || publicBackground || null;
}
if (
runProfileId &&
publicWorkDetail &&
isMatch3DGalleryEntry(publicWorkDetail) &&
publicWorkDetail.profileId === runProfileId
) {
return publicBackground || profileBackground || null;
}
return profileBackground || publicBackground || null;
}
function resolveMatch3DGenerationStateFromAssets(
@@ -699,7 +773,7 @@ function resolveMatch3DGenerationStateFromAssets(
...current,
phase:
imageReadyCount > 0 || modelReadyCount > 0
? 'match3d-generate-models'
? 'match3d-generate-views'
: current.phase,
completedAssetCount: modelReadyCount,
totalAssetCount,
@@ -870,6 +944,11 @@ const PUZZLE_ONBOARDING_COPY = '待定待定待定';
const PUZZLE_ONBOARDING_CLEAR_COPY = '只差一步,就可以永久保留你的梦';
const PUZZLE_ONBOARDING_GENERATED_DELAY_MS = 700;
function isPuzzleOnboardingEnabled(): boolean {
// 中文注释:当前产品要求隐藏首访新手引导,旧流程暂时保留用于后续显式恢复。
return false;
}
function escapePuzzleOnboardingSvgText(value: string) {
return value
.replace(/&/gu, '&')
@@ -1020,6 +1099,10 @@ function maybeAlertWorkNotFoundAndReturnHome() {
}
function hasSeenPuzzleOnboarding() {
if (!isPuzzleOnboardingEnabled()) {
return true;
}
if (typeof window === 'undefined') {
return true;
}
@@ -1946,6 +2029,8 @@ export function PlatformEntryFlowShellImpl({
>([]);
const [match3dProfile, setMatch3DProfile] =
useState<Match3DWorkProfile | null>(null);
const [match3dRuntimeProfile, setMatch3DRuntimeProfile] =
useState<Match3DWorkProfile | null>(null);
const [match3dRun, setMatch3DRun] = useState<Match3DRunSnapshot | null>(null);
const [match3dRuntimeReturnStage, setMatch3DRuntimeReturnStage] = useState<
'match3d-result' | 'work-detail'
@@ -3283,6 +3368,7 @@ export function PlatformEntryFlowShellImpl({
session.sessionId,
]);
markPendingDraftGenerating('big-fish', session.sessionId);
selectionStageRef.current = 'big-fish-generating';
setSelectionStage('big-fish-generating');
setBigFishGenerationState(createMiniGameDraftGenerationState('big-fish'));
},
@@ -3358,20 +3444,24 @@ export function PlatformEntryFlowShellImpl({
const profileId = response.session.draft?.profileId;
if (!profileId) {
setMatch3DProfile(null);
setMatch3DRuntimeProfile(null);
return { openResult };
}
let runtimeProfile: Match3DWorkProfile | null = null;
try {
const { item } = await getMatch3DWorkDetail(profileId);
setMatch3DProfile({
runtimeProfile = {
...item,
generatedItemAssets:
response.session.draft?.generatedItemAssets ??
item.generatedItemAssets,
});
};
setMatch3DProfile(runtimeProfile);
await refreshMatch3DShelf().catch(() => undefined);
} catch {
setMatch3DProfile(buildMatch3DProfileFromSession(response.session));
runtimeProfile = buildMatch3DProfileFromSession(response.session);
setMatch3DProfile(runtimeProfile);
}
markPendingDraftReady('match3d', response.session.sessionId, openResult);
markDraftReady(
@@ -3379,6 +3469,26 @@ export function PlatformEntryFlowShellImpl({
[profileId, response.session.sessionId],
openResult,
);
if (openResult && runtimeProfile) {
try {
await preloadMatch3DGeneratedModelAssets(
runtimeProfile.generatedItemAssets,
{ expireSeconds: 300 },
);
const { run } = await startMatch3DRun(runtimeProfile.profileId);
setMatch3DRuntimeProfile(runtimeProfile);
setMatch3DRun(run);
setMatch3DProfile(runtimeProfile);
setMatch3DRuntimeReturnStage('match3d-result');
setSelectionStage('match3d-runtime');
} catch (error) {
setMatch3DError(
resolveMatch3DErrorMessage(error, '启动抓大鹅玩法失败。'),
);
setSelectionStage('match3d-result');
}
return { openResult: false };
}
return { openResult };
},
beforeExecuteAction: ({ payload, session }) => {
@@ -3391,6 +3501,7 @@ export function PlatformEntryFlowShellImpl({
session.sessionId,
]);
markPendingDraftGenerating('match3d', session.sessionId);
selectionStageRef.current = 'match3d-generating';
setSelectionStage('match3d-generating');
setMatch3DGenerationState(createMiniGameDraftGenerationState('match3d'));
},
@@ -3468,6 +3579,7 @@ export function PlatformEntryFlowShellImpl({
setSquareHoleGenerationState(
createMiniGameDraftGenerationState('square-hole'),
);
selectionStageRef.current = 'square-hole-generating';
setSelectionStage('square-hole-generating');
}
if (payload.action === 'square_hole_generate_visual_assets') {
@@ -3484,6 +3596,7 @@ export function PlatformEntryFlowShellImpl({
totalAssetCount: 0,
error: null,
}));
selectionStageRef.current = 'square-hole-generating';
setSelectionStage('square-hole-generating');
}
},
@@ -3746,6 +3859,46 @@ export function PlatformEntryFlowShellImpl({
openResult,
);
void refreshPuzzleShelf();
if (openResult && response.session.draft) {
const draft = response.session.draft;
const draftProfileId =
response.session.publishedProfileId ??
buildPuzzleResultProfileId(response.session.sessionId);
if (!draft.coverImageSrc || !draftProfileId) {
setPuzzleError(
!draft.coverImageSrc
? '请先选择一张正式拼图图片。'
: '这份拼图草稿缺少会话信息,请重新开始创作。',
);
setSelectionStage('puzzle-result');
return { openResult: false };
}
try {
const { item } = await updatePuzzleWork(draftProfileId, {
workTitle: draft.workTitle,
workDescription: draft.workDescription,
levelName: draft.levelName,
summary: draft.summary,
themeTags: draft.themeTags,
coverImageSrc: draft.coverImageSrc,
coverAssetId: draft.coverAssetId,
levels: draft.levels ?? [],
});
const run = startLocalPuzzleRun(item);
setSelectedPuzzleDetail(item);
setPuzzleRun(run);
setPuzzleRuntimeAuthMode('default');
setPuzzleRuntimeReturnStage('puzzle-result');
setSelectionStage('puzzle-runtime');
} catch (error) {
setPuzzleError(
resolvePuzzleErrorMessage(error, '启动拼图试玩失败。'),
);
setSelectionStage('puzzle-result');
}
return { openResult: false };
}
return { openResult };
}
@@ -3792,6 +3945,7 @@ export function PlatformEntryFlowShellImpl({
buildPuzzleResultProfileId(session.sessionId),
]);
markPendingDraftGenerating('puzzle', session.sessionId);
selectionStageRef.current = 'puzzle-generating';
setSelectionStage('puzzle-generating');
setPuzzleGenerationState(createMiniGameDraftGenerationState('puzzle'));
},
@@ -4089,6 +4243,7 @@ export function PlatformEntryFlowShellImpl({
setMatch3DGenerationState(null);
setMatch3DSession(null);
setMatch3DProfile(null);
setMatch3DRuntimeProfile(null);
setMatch3DRun(null);
setMatch3DError(null);
setStreamingMatch3DReplyText('');
@@ -4102,7 +4257,10 @@ export function PlatformEntryFlowShellImpl({
markPendingDraftGenerating('match3d', nextSession.sessionId);
await match3dFlow.executeAction(
{ action: 'match3d_compile_draft' },
{
action: 'match3d_compile_draft',
generateClickSound: payload.generateClickSound,
},
nextSession,
);
},
@@ -4258,6 +4416,7 @@ export function PlatformEntryFlowShellImpl({
setBigFishError(null);
setMatch3DSession(null);
setMatch3DProfile(null);
setMatch3DRuntimeProfile(null);
setMatch3DFormDraftPayload(null);
setActiveCreationFormType('puzzle');
setMatch3DWorks([]);
@@ -4436,6 +4595,7 @@ export function PlatformEntryFlowShellImpl({
const leaveMatch3DFlow = useCallback(() => {
setMatch3DRun(null);
setMatch3DRuntimeProfile(null);
setMatch3DFormDraftPayload(null);
setMatch3DGenerationState(null);
setMatch3DRuntimeReturnStage('match3d-result');
@@ -4888,6 +5048,7 @@ export function PlatformEntryFlowShellImpl({
void executeMatch3DAction({
action: 'match3d_compile_draft',
generateClickSound: match3dFormDraftPayload?.generateClickSound,
});
}, [
createMatch3DDraftFromForm,
@@ -5322,7 +5483,7 @@ export function PlatformEntryFlowShellImpl({
profile: Match3DWorkProfile | Match3DWorkSummary,
returnStage: 'match3d-result' | 'work-detail' = 'match3d-result',
mirrorErrorToPublicDetail = false,
options: { embedded?: boolean } = {},
options: { embedded?: boolean; itemTypeCountOverride?: number } = {},
) => {
if (isMatch3DBusy) {
return false;
@@ -5333,7 +5494,7 @@ export function PlatformEntryFlowShellImpl({
try {
let runtimeProfile = profile;
if ((profile.generatedItemAssets ?? []).length === 0) {
if (!hasMatch3DGeneratedModelAsset(profile.generatedItemAssets)) {
try {
const { item } = await getMatch3DWorkDetail(profile.profileId);
runtimeProfile = item;
@@ -5341,12 +5502,22 @@ export function PlatformEntryFlowShellImpl({
// 中文注释:详情补读只为拿完整生成素材;失败时继续按摘要开局,避免推荐流卡死。
}
}
const { run } = options.embedded
? await startMatch3DRun(
runtimeProfile.profileId,
RECOMMEND_RUNTIME_BACKGROUND_AUTH_OPTIONS,
)
: await startMatch3DRun(runtimeProfile.profileId);
await preloadMatch3DGeneratedModelAssets(
runtimeProfile.generatedItemAssets,
{ expireSeconds: 300 },
);
const runtimeOptions = {
...(options.embedded ? RECOMMEND_RUNTIME_BACKGROUND_AUTH_OPTIONS : {}),
...(typeof options.itemTypeCountOverride === 'number'
? { itemTypeCountOverride: options.itemTypeCountOverride }
: {}),
};
const { run } =
Object.keys(runtimeOptions).length > 0
? await startMatch3DRun(runtimeProfile.profileId, runtimeOptions)
: await startMatch3DRun(runtimeProfile.profileId);
// 中文注释:运行态必须锁定本次启动时的完整 profile避免首次切屏渲染读到旧草稿 state。
setMatch3DRuntimeProfile(runtimeProfile);
setMatch3DRun(run);
setMatch3DProfile(runtimeProfile);
setMatch3DRuntimeReturnStage(returnStage);
@@ -5492,18 +5663,18 @@ export function PlatformEntryFlowShellImpl({
const startPuzzleTestRunFromDraft = useCallback(
async (draft: PuzzleResultDraft) => {
if (isPuzzleBusy) {
return;
return false;
}
if (!draft.coverImageSrc) {
setPuzzleError('请先选择一张正式拼图图片。');
return;
return false;
}
const profileId =
puzzleSession?.publishedProfileId ??
buildPuzzleResultProfileId(puzzleSession?.sessionId);
if (!profileId) {
setPuzzleError('这份拼图草稿缺少会话信息,请重新开始创作。');
return;
return false;
}
setIsPuzzleBusy(true);
@@ -5525,8 +5696,10 @@ export function PlatformEntryFlowShellImpl({
setPuzzleRuntimeAuthMode('default');
setPuzzleRuntimeReturnStage('puzzle-result');
setSelectionStage('puzzle-runtime');
return true;
} catch (error) {
setPuzzleError(resolvePuzzleErrorMessage(error, '启动拼图试玩失败。'));
return false;
} finally {
setIsPuzzleBusy(false);
}
@@ -7168,6 +7341,7 @@ export function PlatformEntryFlowShellImpl({
setMatch3DRun(null);
setMatch3DError(null);
setMatch3DProfile(null);
setMatch3DRuntimeProfile(null);
markDraftNoticeSeen(
collectDraftNoticeKeys('match3d', [
item.workId,
@@ -7789,6 +7963,11 @@ export function PlatformEntryFlowShellImpl({
}
if (activeRecommendRuntimeKind === 'match3d') {
const activeMatch3DRuntimeProfile = resolveActiveMatch3DRuntimeProfile(
match3dRun,
match3dRuntimeProfile,
match3dProfile,
);
return (
<Match3DRuntimeShell
run={match3dRun}
@@ -7797,7 +7976,12 @@ export function PlatformEntryFlowShellImpl({
embedded
generatedItemAssets={resolveMatch3DRuntimeGeneratedItemAssets(
match3dRun,
match3dProfile,
activeMatch3DRuntimeProfile,
activeEntry,
)}
backgroundImageSrc={resolveMatch3DRuntimeBackgroundImageSrc(
match3dRun,
activeMatch3DRuntimeProfile,
activeEntry,
)}
onBack={() => {
@@ -8004,6 +8188,7 @@ export function PlatformEntryFlowShellImpl({
match3dFlow,
match3dProfile,
match3dRun,
match3dRuntimeProfile,
platformBootstrap.platformTab,
platformThemeClass,
puzzleError,
@@ -9733,9 +9918,15 @@ export function PlatformEntryFlowShellImpl({
stage: 'work-detail',
});
}}
onStartTestRun={(profile) => {
onStartTestRun={(profile, options) => {
setMatch3DProfile(profile);
void startMatch3DRunFromProfile(profile, 'match3d-result');
setMatch3DRuntimeProfile(profile);
void startMatch3DRunFromProfile(
profile,
'match3d-result',
false,
options,
);
}}
/>
</Suspense>
@@ -9743,6 +9934,14 @@ export function PlatformEntryFlowShellImpl({
)}
{selectionStage === 'match3d-runtime' && (
(() => {
const activeMatch3DRuntimeProfile =
resolveActiveMatch3DRuntimeProfile(
match3dRun,
match3dRuntimeProfile,
match3dProfile,
);
return (
<motion.div
key="match3d-runtime"
initial={{ opacity: 0 }}
@@ -9759,7 +9958,12 @@ export function PlatformEntryFlowShellImpl({
error={match3dError}
generatedItemAssets={resolveMatch3DRuntimeGeneratedItemAssets(
match3dRun,
match3dProfile,
activeMatch3DRuntimeProfile,
selectedPublicWorkDetail,
)}
backgroundImageSrc={resolveMatch3DRuntimeBackgroundImageSrc(
match3dRun,
activeMatch3DRuntimeProfile,
selectedPublicWorkDetail,
)}
onBack={() => {
@@ -9824,6 +10028,8 @@ export function PlatformEntryFlowShellImpl({
/>
</Suspense>
</motion.div>
);
})()
)}
{selectionStage === 'square-hole-agent-workspace' && (