Merge branch 'master' into codex/sse-stream-architecture
# Conflicts: # .hermes/shared-memory/decision-log.md # docs/【玩法创作】平台入口与玩法链路-2026-05-15.md # src/components/platform-entry/PlatformEntryFlowShellImpl.tsx
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import { ArrowRight, LockKeyhole } from 'lucide-react';
|
||||
|
||||
import type { CreationEntryConfig } from '../../services/creationEntryConfigService';
|
||||
import { UnifiedModal } from '../common/UnifiedModal';
|
||||
@@ -33,6 +33,7 @@ function CreationTypeCard(props: {
|
||||
}) {
|
||||
const { item, busy, onSelect } = props;
|
||||
const disabled = item.locked || busy;
|
||||
const lockedBadge = item.badge.trim() || '暂未开放';
|
||||
|
||||
return (
|
||||
<button
|
||||
@@ -60,12 +61,15 @@ function CreationTypeCard(props: {
|
||||
/>
|
||||
<div className="relative z-10 flex min-h-6 items-start justify-end gap-3 px-4 pt-4">
|
||||
{item.locked ? (
|
||||
<span className="platform-pill platform-pill--neutral px-3 text-[var(--platform-text-soft)]">
|
||||
{item.badge}
|
||||
<span className="platform-pill platform-pill--neutral gap-1 px-3 text-[var(--platform-text-soft)]">
|
||||
<LockKeyhole className="h-3.5 w-3.5" />
|
||||
{lockedBadge}
|
||||
</span>
|
||||
) : null}
|
||||
{item.locked ? (
|
||||
<span className="text-lg leading-none text-white/62">·</span>
|
||||
<span className="inline-flex h-7 w-7 items-center justify-center rounded-full bg-white/18 text-white/72">
|
||||
<LockKeyhole className="h-3.5 w-3.5" />
|
||||
</span>
|
||||
) : (
|
||||
<ArrowRight className="h-4 w-4 text-white/80" />
|
||||
)}
|
||||
@@ -169,7 +173,6 @@ export function PlatformEntryCreationTypeModal({
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
</UnifiedModal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -414,6 +414,7 @@ import {
|
||||
buildPlatformTaskCompletionDialogDismissKey,
|
||||
formatPlatformDialogSource,
|
||||
isBackgroundGenerationStillRunningMessage,
|
||||
normalizePlatformDialogMessage,
|
||||
PLATFORM_TASK_COMPLETION_MESSAGE,
|
||||
type PlatformDialogCandidate,
|
||||
type PlatformErrorDialogState,
|
||||
@@ -561,6 +562,7 @@ import {
|
||||
buildPlatformPublicGalleryFeeds,
|
||||
getPlatformPublicGalleryEntryKey,
|
||||
getPlatformRecommendRuntimeKind,
|
||||
isPlatformRecommendRuntimeReadyForEntry,
|
||||
isSamePlatformPublicGalleryEntry,
|
||||
type RecommendRuntimeKind,
|
||||
resolvePlatformRecommendRuntimeAutoStartDecision,
|
||||
@@ -744,6 +746,19 @@ const PUZZLE_DRAFT_GENERATION_POINT_COST = 2;
|
||||
const MATCH3D_DRAFT_GENERATION_POINT_COST = 10;
|
||||
const BARK_BATTLE_DRAFT_GENERATION_POINT_COST = 3;
|
||||
|
||||
/** 中文注释:首页公开数据降级时,入口关闭错误不弹窗;真实创作动作仍由对应工作台提示。 */
|
||||
function isCreationEntryDisabledErrorMessage(
|
||||
message: string | null | undefined,
|
||||
) {
|
||||
const normalized = normalizePlatformDialogMessage(message);
|
||||
return Boolean(
|
||||
normalized &&
|
||||
(normalized.includes('creation_entry_disabled') ||
|
||||
normalized.includes('该玩法入口暂不可用') ||
|
||||
normalized.includes('创作入口已关闭')),
|
||||
);
|
||||
}
|
||||
|
||||
const PUZZLE_ONBOARDING_FIRST_VISIT_STORAGE_KEY =
|
||||
'genarrative.puzzle-onboarding.first-visit.v1';
|
||||
const PUZZLE_ONBOARDING_COPY = '待定待定待定';
|
||||
@@ -4185,6 +4200,11 @@ export function PlatformEntryFlowShellImpl({
|
||||
isMiniGameDraftGenerating(
|
||||
activePuzzleBackgroundCompileTask?.generationState ?? null,
|
||||
);
|
||||
const platformBootstrapErrorForDisplay = isCreationEntryDisabledErrorMessage(
|
||||
platformBootstrap.platformError,
|
||||
)
|
||||
? null
|
||||
: platformBootstrap.platformError;
|
||||
const [dismissedPlatformErrorDialogKey, setDismissedPlatformErrorDialogKey] =
|
||||
useState<string | null>(null);
|
||||
const [
|
||||
@@ -4207,7 +4227,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
{
|
||||
key: 'platform-bootstrap',
|
||||
source: '平台首页',
|
||||
message: platformBootstrap.platformError,
|
||||
message: platformBootstrapErrorForDisplay,
|
||||
},
|
||||
{
|
||||
key: 'rpg-creation-type',
|
||||
@@ -4378,7 +4398,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
match3dRun?.runId,
|
||||
match3dSession?.sessionId,
|
||||
pendingPlatformTaskFailureDialog,
|
||||
platformBootstrap.platformError,
|
||||
platformBootstrapErrorForDisplay,
|
||||
publicWorkDetailError,
|
||||
puzzleCreationError,
|
||||
puzzleError,
|
||||
@@ -12011,6 +12031,29 @@ export function PlatformEntryFlowShellImpl({
|
||||
isDesktopLayout,
|
||||
]);
|
||||
|
||||
const activeRecommendEntry =
|
||||
activeRecommendEntryKey && !isDesktopLayout
|
||||
? (recommendRuntimeEntries.find(
|
||||
(entry) =>
|
||||
getPlatformPublicGalleryEntryKey(entry) ===
|
||||
activeRecommendEntryKey,
|
||||
) ?? null)
|
||||
: null;
|
||||
const isActiveRecommendRuntimeReady =
|
||||
activeRecommendEntry !== null &&
|
||||
isPlatformRecommendRuntimeReadyForEntry(activeRecommendEntry, {
|
||||
activeKind: activeRecommendRuntimeKind,
|
||||
hasBabyObjectMatchDraft: Boolean(babyObjectMatchDraft),
|
||||
hasBigFishRun: Boolean(bigFishRun),
|
||||
hasJumpHopRun: Boolean(jumpHopRun),
|
||||
hasMatch3DRun: Boolean(match3dRun),
|
||||
hasSquareHoleRun: Boolean(squareHoleRun),
|
||||
hasVisualNovelRun: Boolean(visualNovelRun),
|
||||
hasWoodenFishRun: Boolean(woodenFishRun),
|
||||
puzzleRunEntryProfileId: puzzleRun?.entryProfileId ?? null,
|
||||
puzzleRunCurrentLevelProfileId: puzzleRun?.currentLevel?.profileId ?? null,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const decision = resolvePlatformRecommendRuntimeAutoStartDecision({
|
||||
isDesktopLayout,
|
||||
@@ -12972,7 +13015,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
(isVisualNovelCreationOpen && isVisualNovelLoadingLibrary) ||
|
||||
isBabyObjectMatchBusy
|
||||
? null
|
||||
: (platformBootstrap.platformError ??
|
||||
: (platformBootstrapErrorForDisplay ??
|
||||
sessionController.agentWorkspaceRestoreError ??
|
||||
bigFishError ??
|
||||
match3dError ??
|
||||
@@ -13093,7 +13136,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
platformError={
|
||||
platformBootstrap.isLoadingPlatform
|
||||
? null
|
||||
: (platformBootstrap.platformError ??
|
||||
: (platformBootstrapErrorForDisplay ??
|
||||
sessionController.agentWorkspaceRestoreError)
|
||||
}
|
||||
dashboardError={
|
||||
@@ -13122,6 +13165,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
onOpenRecommendGalleryDetail={openRecommendGalleryDetail}
|
||||
recommendRuntimeContent={recommendRuntimeContent}
|
||||
activeRecommendEntryKey={activeRecommendEntryKey}
|
||||
isRecommendRuntimeReady={isActiveRecommendRuntimeReady}
|
||||
isStartingRecommendEntry={
|
||||
isStartingRecommendEntry ||
|
||||
isBigFishBusy ||
|
||||
|
||||
@@ -58,6 +58,22 @@ describe('PlatformErrorDialog', () => {
|
||||
|
||||
expect(screen.queryByRole('dialog', { name: '发生错误' })).toBeNull();
|
||||
});
|
||||
|
||||
test('does not render creation entry disabled errors', () => {
|
||||
render(
|
||||
<PlatformErrorDialog
|
||||
error={{
|
||||
source: '大鱼草稿',
|
||||
message:
|
||||
'creation_entry_disabled(requestId: req-big-fish-gallery)',
|
||||
}}
|
||||
onClose={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByRole('dialog', { name: '发生错误' })).toBeNull();
|
||||
expect(screen.queryByText(/creation_entry_disabled/u)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('PlatformTaskCompletionDialog', () => {
|
||||
|
||||
@@ -20,6 +20,11 @@ function buildPlatformErrorReport(error: PlatformErrorDialogPayload) {
|
||||
return [`来源:${error.source}`, `错误:${error.message}`].join('\n');
|
||||
}
|
||||
|
||||
function isBlacklistedPlatformError(error: PlatformErrorDialogPayload | null) {
|
||||
// 中文注释:入口关闭是平台开关状态,不作为全局错误弹窗打扰用户。
|
||||
return Boolean(error?.message.includes('creation_entry_disabled'));
|
||||
}
|
||||
|
||||
export function PlatformErrorDialog({
|
||||
error,
|
||||
onClose,
|
||||
@@ -30,9 +35,10 @@ export function PlatformErrorDialog({
|
||||
'idle',
|
||||
);
|
||||
const resetTimerRef = useRef<number | null>(null);
|
||||
const dialogError = isBlacklistedPlatformError(error) ? null : error;
|
||||
const reportText = useMemo(
|
||||
() => (error ? buildPlatformErrorReport(error) : ''),
|
||||
[error],
|
||||
() => (dialogError ? buildPlatformErrorReport(dialogError) : ''),
|
||||
[dialogError],
|
||||
);
|
||||
|
||||
useEffect(
|
||||
@@ -46,7 +52,7 @@ export function PlatformErrorDialog({
|
||||
|
||||
useEffect(() => {
|
||||
setCopyState('idle');
|
||||
}, [error?.source, error?.message]);
|
||||
}, [dialogError?.source, dialogError?.message]);
|
||||
|
||||
const copyError = () => {
|
||||
if (!reportText) {
|
||||
@@ -67,7 +73,7 @@ export function PlatformErrorDialog({
|
||||
|
||||
return (
|
||||
<UnifiedModal
|
||||
open={Boolean(error)}
|
||||
open={Boolean(dialogError)}
|
||||
title="发生错误"
|
||||
onClose={onClose}
|
||||
size="sm"
|
||||
@@ -95,14 +101,14 @@ export function PlatformErrorDialog({
|
||||
</button>
|
||||
}
|
||||
>
|
||||
{error ? (
|
||||
{dialogError ? (
|
||||
<>
|
||||
<div className="rounded-[1rem] border border-[var(--platform-subpanel-border)] bg-white/72 px-3 py-2">
|
||||
<div className="text-xs font-bold text-[var(--platform-text-soft)]">
|
||||
来源
|
||||
</div>
|
||||
<div className="mt-1 break-words text-sm font-semibold leading-5 text-[var(--platform-text-strong)]">
|
||||
{error.source}
|
||||
{dialogError.source}
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-[1rem] border border-[var(--platform-subpanel-border)] bg-white/72 px-3 py-2">
|
||||
@@ -110,7 +116,7 @@ export function PlatformErrorDialog({
|
||||
错误
|
||||
</div>
|
||||
<div className="mt-1 whitespace-pre-wrap break-words text-sm leading-6 text-[var(--platform-text-strong)]">
|
||||
{error.message}
|
||||
{dialogError.message}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user