fix: refresh custom world publish gate diagnostics

This commit is contained in:
2026-04-24 12:42:43 +08:00
parent 5050ce4ff8
commit 49a79aee54
5 changed files with 321 additions and 6 deletions

View File

@@ -92,6 +92,147 @@ import { usePlatformEntryBootstrap } from './usePlatformEntryBootstrap';
import { usePlatformEntryLibraryDetail } from './usePlatformEntryLibraryDetail';
import { usePlatformEntryNavigation } from './usePlatformEntryNavigation';
type AgentResultPublishGateView = {
blockers: string[];
publishReady: boolean;
};
type AgentResultBlockerView = {
code?: string;
message: string;
};
const AGENT_RESULT_STRUCTURAL_BLOCKER_CODES = new Set([
'publish_missing_world_hook',
'publish_missing_player_premise',
'publish_missing_core_conflict',
'publish_missing_main_chapter',
'publish_missing_first_act',
]);
function readProfileTextField(
profile: CustomWorldProfile | null,
paths: string[],
) {
for (const path of paths) {
let current: unknown = profile;
for (const segment of path.split('.')) {
if (!current || typeof current !== 'object') {
current = null;
break;
}
current = (current as Record<string, unknown>)[segment];
}
if (typeof current === 'string' && current.trim()) {
return current.trim();
}
}
return null;
}
function hasProfileTextArray(profile: CustomWorldProfile | null, key: string) {
const value = profile
? (profile as unknown as Record<string, unknown>)[key]
: null;
return Array.isArray(value)
? value.some((entry) => typeof entry === 'string' && entry.trim())
: false;
}
function hasProfileArray(profile: CustomWorldProfile | null, key: string) {
const value = profile
? (profile as unknown as Record<string, unknown>)[key]
: null;
return Array.isArray(value) && value.length > 0;
}
function hasSceneAct(profile: CustomWorldProfile | null) {
const rawProfile = profile as unknown as Record<string, unknown> | null;
const chapters =
rawProfile &&
(Array.isArray(rawProfile.sceneChapterBlueprints)
? rawProfile.sceneChapterBlueprints
: Array.isArray(rawProfile.sceneChapters)
? rawProfile.sceneChapters
: []);
return Array.isArray(chapters)
? chapters.some((chapter) => {
const acts =
chapter && typeof chapter === 'object'
? (chapter as Record<string, unknown>).acts
: null;
return Array.isArray(acts) && acts.length > 0;
})
: false;
}
function isAgentResultStructuralBlockerResolved(
profile: CustomWorldProfile,
code: string | undefined,
) {
if (!code || !AGENT_RESULT_STRUCTURAL_BLOCKER_CODES.has(code)) {
return false;
}
if (code === 'publish_missing_world_hook') {
return Boolean(
readProfileTextField(profile, [
'worldHook',
'creatorIntent.worldHook',
'anchorContent.worldPromise.hook',
'settingText',
]),
);
}
if (code === 'publish_missing_player_premise') {
return Boolean(
readProfileTextField(profile, [
'playerPremise',
'creatorIntent.playerPremise',
'anchorContent.playerEntryPoint.openingIdentity',
'anchorContent.playerEntryPoint.openingProblem',
'anchorContent.playerEntryPoint.entryMotivation',
]),
);
}
if (code === 'publish_missing_core_conflict') {
return hasProfileTextArray(profile, 'coreConflicts');
}
if (code === 'publish_missing_main_chapter') {
return (
hasProfileArray(profile, 'chapters') ||
hasProfileArray(profile, 'sceneChapterBlueprints') ||
hasProfileArray(profile, 'sceneChapters')
);
}
return hasSceneAct(profile);
}
function buildAgentResultPublishGateView(
profile: CustomWorldProfile | null,
fallbackBlockers: AgentResultBlockerView[],
fallbackPublishReady: boolean,
): AgentResultPublishGateView {
if (!profile) {
return {
blockers: fallbackBlockers.map((entry) => entry.message),
publishReady: fallbackPublishReady,
};
}
const blockers = fallbackBlockers
.filter(
(entry) =>
!isAgentResultStructuralBlockerResolved(profile, entry.code),
)
.map((entry) => entry.message);
return {
blockers,
publishReady: blockers.length === 0,
};
}
const CustomWorldGenerationView = lazy(async () => {
const module = await import('../CustomWorldGenerationView');
return {
@@ -318,9 +459,22 @@ export function PlatformEntryFlowShellImpl({
const agentResultPreview =
sessionController.agentSession?.resultPreview ?? null;
const agentResultPreviewBlockers = useMemo(
() => agentResultPreview?.blockers?.map((entry) => entry.message) ?? [],
() => agentResultPreview?.blockers ?? [],
[agentResultPreview],
);
const agentResultPublishGateView = useMemo(
() =>
buildAgentResultPublishGateView(
sessionController.generatedCustomWorldProfile,
agentResultPreviewBlockers,
Boolean(agentResultPreview?.publishReady),
),
[
agentResultPreview?.publishReady,
agentResultPreviewBlockers,
sessionController.generatedCustomWorldProfile,
],
);
const agentResultPreviewQualityFindings = useMemo(
() => agentResultPreview?.qualityFindings ?? [],
[agentResultPreview],
@@ -1928,12 +2082,12 @@ export function PlatformEntryFlowShellImpl({
}
publishReady={
sessionController.isAgentDraftResultView
? Boolean(agentResultPreview?.publishReady)
? agentResultPublishGateView.publishReady
: true
}
publishBlockers={
sessionController.isAgentDraftResultView
? agentResultPreviewBlockers
? agentResultPublishGateView.blockers
: []
}
qualityFindings={

View File

@@ -1,4 +1,4 @@
import { useMemo, useState } from 'react';
import { useMemo, useState } from 'react';
import type { Character, CustomWorldProfile } from '../../types';
import {
@@ -201,6 +201,23 @@ export function RpgCreationResultView({
{error}
</div>
) : null}
{!error && compactAgentResultMode && previewSourceLabel ? (
<div className="platform-banner platform-banner--info mt-3 rounded-2xl text-sm leading-6">
{previewSourceLabel}
</div>
) : null}
{!error && compactAgentResultMode && publishBlockers.length > 0 ? (
<div className="platform-banner platform-banner--warning mt-3 rounded-2xl text-sm leading-6">
{publishReady
? '当前世界已满足发布门槛。'
: `当前还有 ${publishBlockers.length} 个发布阻断项,请先补齐后再进入世界。`}
{!publishReady ? (
<div className="mt-2 text-xs text-[var(--platform-text-muted)]">
</div>
) : null}
</div>
) : null}
{!error &&
compactAgentResultMode &&
publishBlockers.length <= 0 &&

View File

@@ -192,8 +192,14 @@ export function useRpgCreationResultAutosave(
const latestSessionProfileSignature = latestSessionProfile
? stringifyAgentBackedProfile(latestSessionProfile)
: '';
const shouldRefreshPublishGate = Boolean(
agentSession?.resultPreview && !agentSession.resultPreview.publishReady,
);
if (latestSessionProfileSignature === profileSignature) {
if (
latestSessionProfileSignature === profileSignature &&
!shouldRefreshPublishGate
) {
latestAgentResultSyncSignatureRef.current = profileSignature;
return {
session: agentSession,
@@ -201,7 +207,10 @@ export function useRpgCreationResultAutosave(
} satisfies SyncedAgentDraftResult;
}
if (latestAgentResultSyncSignatureRef.current === profileSignature) {
if (
latestAgentResultSyncSignatureRef.current === profileSignature &&
!shouldRefreshPublishGate
) {
return {
session: agentSession,
profile: normalizeAgentBackedProfile(latestSessionProfile ?? profile),