merge: database backed creation entry config

# Conflicts:
#	src/components/platform-entry/PlatformEntryFlowShellImpl.tsx
This commit is contained in:
2026-05-11 11:25:35 +08:00
37 changed files with 1458 additions and 204 deletions

View File

@@ -1,4 +1,4 @@
import { ArrowRight, Loader2, Sparkles } from 'lucide-react';
import { ArrowRight, Loader2, Sparkles } from 'lucide-react';
import { AnimatePresence, motion } from 'motion/react';
import {
type Dispatch,
@@ -99,10 +99,15 @@ import {
buildPublicWorkStagePath,
pushAppHistoryPath,
} from '../../routing/appPageRoutes';
import { resolveRuntimeNotFoundRecoveryAction } from '../../routing/runtimeNotFoundRecovery';
import {
ApiClientError,
BACKGROUND_AUTH_REQUEST_OPTIONS,
} from '../../services/apiClient';
import {
fetchCreationEntryConfig,
type CreationEntryConfig,
} from '../../services/creationEntryConfigService';
import {
getPublicAuthUserByCode,
getPublicAuthUserById,
@@ -299,6 +304,7 @@ import {
import { PlatformEntryCreationTypeModal } from './PlatformEntryCreationTypeModal';
import type { PlatformCreationTypeId } from './platformEntryCreationTypes';
import {
derivePlatformCreationTypes,
getVisiblePlatformCreationTypes,
isPlatformCreationTypeVisible,
} from './platformEntryCreationTypes';
@@ -883,6 +889,24 @@ function isMissingPuzzleWorkError(error: unknown) {
);
}
function maybeAlertRuntimeNotFoundAndReturnHome() {
if (typeof window === 'undefined') {
return false;
}
const recoveryAction = resolveRuntimeNotFoundRecoveryAction(
window.location.pathname,
);
if (!recoveryAction) {
return false;
}
// 中文注释:直接 runtime 深链找不到作品时,弹窗确认后立刻回首页,避免保留空白运行态。
window.alert('作品不存在或已下架,将返回首页。');
pushAppHistoryPath(recoveryAction.nextPath);
return true;
}
function hasSeenPuzzleOnboarding() {
if (typeof window === 'undefined') {
return true;
@@ -1699,7 +1723,22 @@ export function PlatformEntryFlowShellImpl({
] = useState<string | null>(null);
const [publishSharePayload, setPublishSharePayload] =
useState<PublishShareModalPayload | null>(null);
const isBigFishCreationVisible = isPlatformCreationTypeVisible('big-fish');
const [creationEntryConfig, setCreationEntryConfig] =
useState<CreationEntryConfig | null>(null);
const [creationEntryConfigError, setCreationEntryConfigError] = useState<
string | null
>(null);
const creationEntryTypes = useMemo(
() =>
creationEntryConfig
? derivePlatformCreationTypes(creationEntryConfig.creationTypes)
: [],
[creationEntryConfig],
);
const isBigFishCreationVisible = isPlatformCreationTypeVisible(
creationEntryTypes,
'big-fish',
);
const [profilePlayStats, setProfilePlayStats] =
useState<ProfilePlayStatsResponse | null>(null);
const [profilePlayStatsError, setProfilePlayStatsError] = useState<
@@ -1715,6 +1754,27 @@ export function PlatformEntryFlowShellImpl({
);
const handledInitialPublicWorkCodeRef = useRef<string | null>(null);
useEffect(() => {
let cancelled = false;
setCreationEntryConfigError(null);
void fetchCreationEntryConfig()
.then((config) => {
if (!cancelled) {
setCreationEntryConfig(config);
}
})
.catch((error: unknown) => {
if (!cancelled) {
setCreationEntryConfigError(
error instanceof Error ? error.message : '读取创作入口配置失败。',
);
}
});
return () => {
cancelled = true;
};
}, []);
const platformBootstrap = usePlatformEntryBootstrap({
user: authUi?.user,
canAccessProtectedData: authUi?.canAccessProtectedData,
@@ -4274,7 +4334,9 @@ export function PlatformEntryFlowShellImpl({
setPublicWorkDetailError(null);
setPlatformTab('home');
setSelectionStage('platform');
pushAppHistoryPath('/');
if (!maybeAlertRuntimeNotFoundAndReturnHome()) {
pushAppHistoryPath('/');
}
return false;
}
@@ -5774,7 +5836,9 @@ export function PlatformEntryFlowShellImpl({
setPublicWorkDetailError(null);
setPlatformTab('home');
setSelectionStage('platform');
pushAppHistoryPath('/');
if (!maybeAlertRuntimeNotFoundAndReturnHome()) {
pushAppHistoryPath('/');
}
return;
}
@@ -5993,7 +6057,9 @@ export function PlatformEntryFlowShellImpl({
setPublicWorkDetailError(null);
setPlatformTab('home');
setSelectionStage('platform');
pushAppHistoryPath('/');
if (!maybeAlertRuntimeNotFoundAndReturnHome()) {
pushAppHistoryPath('/');
}
return;
}
@@ -7567,7 +7633,8 @@ export function PlatformEntryFlowShellImpl({
fallbackLabel: string,
) => (
<Suspense fallback={<LazyPanelFallback label={fallbackLabel} />}>
<CustomWorldCreationHub
{creationEntryConfig ? (
<CustomWorldCreationHub
mode={mode}
items={creationHubItems}
loading={
@@ -7597,6 +7664,14 @@ export function PlatformEntryFlowShellImpl({
}
onRetry={() => {
platformBootstrap.setPlatformError(null);
setCreationEntryConfigError(null);
void fetchCreationEntryConfig()
.then(setCreationEntryConfig)
.catch((error: unknown) => {
setCreationEntryConfigError(
error instanceof Error ? error.message : '读取创作入口配置失败。',
);
});
setBigFishError(null);
setMatch3DError(null);
setSquareHoleError(null);
@@ -7618,6 +7693,7 @@ export function PlatformEntryFlowShellImpl({
void refreshVisualNovelShelf();
}}
createError={
creationEntryConfigError ??
sessionController.creationTypeError ??
bigFishError ??
match3dError ??
@@ -7627,6 +7703,7 @@ export function PlatformEntryFlowShellImpl({
visualNovelError
}
createBusy={
!creationEntryConfig ||
sessionController.isCreatingAgentSession ||
isCreativeAgentBusy ||
isCreativeAgentStreaming ||
@@ -7637,6 +7714,8 @@ export function PlatformEntryFlowShellImpl({
isVisualNovelBusy ||
isVisualNovelStreamingReply
}
entryConfig={creationEntryConfig}
creationTypes={creationEntryTypes}
onCreateType={handleCreationHubCreateType}
onOpenDraft={(item) => {
runProtectedAction(() => {
@@ -7717,6 +7796,7 @@ export function PlatformEntryFlowShellImpl({
handleDeleteVisualNovelWork(item);
}}
/>
) : null}
</Suspense>
);
const creationStartContent = (
@@ -7728,7 +7808,7 @@ export function PlatformEntryFlowShellImpl({
role="tablist"
aria-label="选择模板"
>
{getVisiblePlatformCreationTypes().map((item) => {
{getVisiblePlatformCreationTypes(creationEntryTypes).map((item) => {
const selected = item.id === activeCreationFormType;
const disabled =
item.locked ||
@@ -9438,7 +9518,8 @@ export function PlatformEntryFlowShellImpl({
)}
</AnimatePresence>
<PlatformEntryCreationTypeModal
{creationEntryConfig ? (
<PlatformEntryCreationTypeModal
isOpen={showCreationTypeModal}
isBusy={
sessionController.isCreatingAgentSession ||
@@ -9452,6 +9533,7 @@ export function PlatformEntryFlowShellImpl({
isVisualNovelStreamingReply
}
error={
creationEntryConfigError ??
bigFishError ??
creativeAgentError ??
match3dError ??
@@ -9461,6 +9543,8 @@ export function PlatformEntryFlowShellImpl({
puzzleError ??
sessionController.creationTypeError
}
entryConfig={creationEntryConfig}
creationTypes={creationEntryTypes}
onClose={() => {
if (
sessionController.isCreatingAgentSession ||
@@ -9507,6 +9591,7 @@ export function PlatformEntryFlowShellImpl({
handleCreationHubCreateType('visual-novel');
}}
/>
) : null}
<PublishShareModal
open={Boolean(publishSharePayload)}
payload={publishSharePayload}